summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Knox <psimyn@gmail.com>2017-12-08 23:13:14 +1100
committerSimon Knox <psimyn@gmail.com>2017-12-08 23:13:14 +1100
commit078ac22567b50a375a59faffad6d441e2d04c22b (patch)
tree8aaf6cf6112609aa8f9498a8d319e54593b68a69
parente47c613d225a256e163af177c886624d81902014 (diff)
parent9429e8ac60a10436a0469d7d206d3f74a2c966c7 (diff)
downloadgitlab-ce-psimyn-issue-note-rename.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into psimyn-issue-note-renamepsimyn-issue-note-rename
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/deploy_keys/components/action_btn.vue9
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue5
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js3
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js2
-rw-r--r--app/assets/javascripts/dispatcher.js7
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue17
-rw-r--r--app/assets/javascripts/issue_show/index.js27
-rw-r--r--app/assets/javascripts/job.js8
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js2
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue2
-rw-r--r--app/assets/javascripts/notes/index.js10
-rw-r--r--app/assets/javascripts/projects/ci_cd_settings_bundle.js19
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js2
-rw-r--r--app/assets/stylesheets/framework/common.scss13
-rw-r--r--app/assets/stylesheets/framework/lists.scss8
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/groups.scss12
-rw-r--r--app/assets/stylesheets/pages/labels.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss21
-rw-r--r--app/controllers/admin/health_check_controller.rb2
-rw-r--r--app/controllers/concerns/renders_member_access.rb23
-rw-r--r--app/controllers/concerns/uploads_actions.rb23
-rw-r--r--app/controllers/dashboard/projects_controller.rb11
-rw-r--r--app/controllers/explore/projects_controller.rb13
-rw-r--r--app/controllers/groups/uploads_controller.rb35
-rw-r--r--app/controllers/health_controller.rb11
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb4
-rw-r--r--app/controllers/projects/commit_controller.rb17
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb42
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb1
-rw-r--r--app/controllers/projects/uploads_controller.rb28
-rw-r--r--app/controllers/users_controller.rb7
-rw-r--r--app/helpers/application_settings_helper.rb19
-rw-r--r--app/helpers/auto_devops_helper.rb16
-rw-r--r--app/helpers/builds_helper.rb3
-rw-r--r--app/helpers/commits_helper.rb8
-rw-r--r--app/helpers/markup_helper.rb2
-rw-r--r--app/helpers/merge_requests_helper.rb24
-rw-r--r--app/helpers/storage_health_helper.rb6
-rw-r--r--app/models/application_setting.rb12
-rw-r--r--app/models/ci/build.rb20
-rw-r--r--app/models/commit.rb3
-rw-r--r--app/models/commit_status.rb3
-rw-r--r--app/models/concerns/bulk_member_access_load.rb46
-rw-r--r--app/models/concerns/discussion_on_diff.rb4
-rw-r--r--app/models/diff_discussion.rb6
-rw-r--r--app/models/diff_note.rb7
-rw-r--r--app/models/discussion.rb1
-rw-r--r--app/models/epic.rb10
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/merge_request.rb27
-rw-r--r--app/models/note.rb20
-rw-r--r--app/models/personal_access_token.rb21
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/project_team.rb37
-rw-r--r--app/models/repository.rb17
-rw-r--r--app/models/user.rb29
-rw-r--r--app/policies/group_policy.rb7
-rw-r--r--app/services/ci/register_job_service.rb3
-rw-r--r--app/services/merge_requests/refresh_service.rb2
-rw-r--r--app/services/metrics_service.rb2
-rw-r--r--app/services/projects/fork_service.rb36
-rw-r--r--app/services/projects/update_service.rb10
-rw-r--r--app/services/protected_branches/access_level_params.rb33
-rw-r--r--app/services/protected_branches/api_service.rb24
-rw-r--r--app/uploaders/file_uploader.rb8
-rw-r--r--app/uploaders/namespace_file_uploader.rb15
-rw-r--r--app/views/admin/application_settings/_form.html.haml18
-rw-r--r--app/views/dashboard/projects/_projects.html.haml2
-rw-r--r--app/views/discussions/_discussion.html.haml14
-rw-r--r--app/views/explore/projects/_projects.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml1
-rw-r--r--app/views/layouts/_search.html.haml9
-rw-r--r--app/views/layouts/group.html.haml6
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml7
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/clusters/_advanced_settings.html.haml8
-rw-r--r--app/views/projects/clusters/_banner.html.haml6
-rw-r--r--app/views/projects/clusters/_dropdown.html.haml2
-rw-r--r--app/views/projects/clusters/gcp/_header.html.haml8
-rw-r--r--app/views/projects/clusters/gcp/login.html.haml2
-rw-r--r--app/views/projects/clusters/gcp/new.html.haml2
-rw-r--r--app/views/projects/clusters/new.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml12
-rw-r--r--app/views/projects/commit/show.html.haml3
-rw-r--r--app/views/projects/commits/_commit.html.haml29
-rw-r--r--app/views/projects/commits/_commits.html.haml7
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/issues/_discussion.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/_commits.html.haml2
-rw-r--r--app/views/projects/merge_requests/diffs/_commit_widget.html.haml5
-rw-r--r--app/views/projects/merge_requests/diffs/_different_base.html.haml11
-rw-r--r--app/views/projects/merge_requests/diffs/_diffs.html.haml25
-rw-r--r--app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml17
-rw-r--r--app/views/projects/merge_requests/diffs/_version_controls.html.haml (renamed from app/views/projects/merge_requests/diffs/_versions.html.haml)26
-rw-r--r--app/views/projects/merge_requests/show.html.haml10
-rw-r--r--app/views/projects/new.html.haml5
-rw-r--r--app/views/projects/notes/_actions.html.haml2
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml23
-rw-r--r--app/views/shared/_outdated_browser.html.haml13
-rw-r--r--app/views/shared/groups/_group.html.haml25
-rw-r--r--app/views/shared/groups/_list.html.haml4
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml6
-rw-r--r--app/views/shared/projects/_project.html.haml11
-rwxr-xr-xbin/storage_check11
-rw-r--r--changelogs/unreleased/15774-fix-39233-500-in-merge-request.yml5
-rw-r--r--changelogs/unreleased/40555-replace-absolute-urls-with-related-branches-to-avoid-hostname.yml6
-rw-r--r--changelogs/unreleased/40573-rename-gke-as-kubernetes-engine.yml5
-rw-r--r--changelogs/unreleased/40715-updateendpoint-undefined-on-issue-page.yml5
-rw-r--r--changelogs/unreleased/bvl-circuitbreaker-process.yml5
-rw-r--r--changelogs/unreleased/deploy-keys-loading-icon.yml5
-rw-r--r--changelogs/unreleased/dm-commit-diff-discussions-in-mr-context.yml5
-rw-r--r--changelogs/unreleased/docs-add-why-do-i-get-signed-out-authentication-section.yml5
-rw-r--r--changelogs/unreleased/feature-sm-34834-missing-dependency-should-fail-job-2.yml5
-rw-r--r--changelogs/unreleased/fix-new-project-guidelines-styling.yml5
-rw-r--r--changelogs/unreleased/fj-40752-forks-api-not-using-services.yml5
-rw-r--r--changelogs/unreleased/merge-request-lock-icon-size-fix.yml5
-rw-r--r--changelogs/unreleased/outdated-browser-position-fix.yml5
-rw-r--r--config/initializers/7_prometheus_metrics.rb9
-rw-r--r--config/routes.rb1
-rw-r--r--config/routes/group.rb6
-rw-r--r--db/migrate/20171123094802_add_circuitbreaker_check_interval_to_application_settings.rb20
-rw-r--r--db/post_migrate/20171123101020_update_circuitbreaker_defaults.rb34
-rw-r--r--db/post_migrate/20171123101046_remove_old_circuitbreaker_config.rb26
-rw-r--r--db/post_migrate/20171205190711_reschedule_fork_network_creation_caller.rb27
-rw-r--r--db/schema.rb9
-rw-r--r--doc/README.md96
-rw-r--r--doc/administration/index.md121
-rw-r--r--doc/administration/job_artifacts.md39
-rw-r--r--doc/administration/pages/index.md3
-rw-r--r--doc/api/protected_branches.md2
-rw-r--r--doc/api/settings.md3
-rw-r--r--doc/ci/autodeploy/quick_start_guide.md4
-rw-r--r--doc/ci/yaml/README.md14
-rw-r--r--doc/install/kubernetes/gitlab_chart.md4
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md4
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md2
-rw-r--r--doc/install/kubernetes/index.md2
-rw-r--r--doc/topics/authentication/index.md1
-rw-r--r--doc/topics/autodevops/img/auto_devops_settings.pngbin67845 -> 95233 bytes
-rw-r--r--doc/topics/autodevops/index.md2
-rw-r--r--doc/topics/autodevops/quick_start_guide.md4
-rw-r--r--doc/user/profile/index.md26
-rw-r--r--doc/user/project/index.md2
-rw-r--r--doc/user/project/pages/index.md121
-rw-r--r--doc/user/project/pipelines/job_artifacts.md18
-rw-r--r--lib/api/circuit_breakers.rb2
-rw-r--r--lib/api/entities.rb3
-rw-r--r--lib/api/projects.rb21
-rw-r--r--lib/api/protected_branches.rb18
-rw-r--r--lib/backup/repository.rb9
-rw-r--r--lib/banzai/cross_project_reference.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb98
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb34
-rw-r--r--lib/banzai/filter/epic_reference_filter.rb12
-rw-r--r--lib/banzai/filter/issuable_reference_filter.rb31
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb32
-rw-r--r--lib/banzai/filter/label_reference_filter.rb4
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb37
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb2
-rw-r--r--lib/banzai/filter/upload_link_filter.rb18
-rw-r--r--lib/banzai/issuable_extractor.rb4
-rw-r--r--lib/banzai/object_renderer.rb12
-rw-r--r--lib/banzai/reference_parser/epic_parser.rb12
-rw-r--r--lib/banzai/reference_parser/issuable_parser.rb25
-rw-r--r--lib/banzai/reference_parser/issue_parser.rb12
-rw-r--r--lib/banzai/reference_parser/merge_request_parser.rb24
-rw-r--r--lib/gitlab/ci/pipeline/chain/create.rb16
-rw-r--r--lib/gitlab/diff/diff_refs.rb22
-rw-r--r--lib/gitlab/diff/inline_diff.rb2
-rw-r--r--lib/gitlab/git.rb12
-rw-r--r--lib/gitlab/git/commit.rb1
-rw-r--r--lib/gitlab/git/operation_service.rb2
-rw-r--r--lib/gitlab/git/remote_repository.rb6
-rw-r--r--lib/gitlab/git/repository.rb50
-rw-r--r--lib/gitlab/git/storage/checker.rb98
-rw-r--r--lib/gitlab/git/storage/circuit_breaker.rb106
-rw-r--r--lib/gitlab/git/storage/circuit_breaker_settings.rb12
-rw-r--r--lib/gitlab/git/storage/failure_info.rb39
-rw-r--r--lib/gitlab/git/storage/null_circuit_breaker.rb22
-rw-r--r--lib/gitlab/git_access.rb6
-rw-r--r--lib/gitlab/git_access_wiki.rb6
-rw-r--r--lib/gitlab/gitaly_client.rb8
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb11
-rw-r--r--lib/gitlab/gitaly_client/wiki_service.rb10
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb6
-rw-r--r--lib/gitlab/reference_extractor.rb2
-rw-r--r--lib/gitlab/storage_check.rb11
-rw-r--r--lib/gitlab/storage_check/cli.rb69
-rw-r--r--lib/gitlab/storage_check/gitlab_caller.rb39
-rw-r--r--lib/gitlab/storage_check/option_parser.rb39
-rw-r--r--lib/gitlab/storage_check/response.rb77
-rw-r--r--locale/bg/gitlab.po16
-rw-r--r--locale/de/gitlab.po16
-rw-r--r--locale/eo/gitlab.po16
-rw-r--r--locale/es/gitlab.po16
-rw-r--r--locale/fr/gitlab.po32
-rw-r--r--locale/gitlab.pot18
-rw-r--r--locale/it/gitlab.po16
-rw-r--r--locale/ja/gitlab.po16
-rw-r--r--locale/ko/gitlab.po16
-rw-r--r--locale/nl_NL/gitlab.po16
-rw-r--r--locale/pl_PL/gitlab.po16
-rw-r--r--locale/pt_BR/gitlab.po32
-rw-r--r--locale/ru/gitlab.po32
-rw-r--r--locale/uk/gitlab.po32
-rw-r--r--locale/zh_CN/gitlab.po32
-rw-r--r--locale/zh_HK/gitlab.po16
-rw-r--r--locale/zh_TW/gitlab.po22
-rw-r--r--qa/qa/page/group/new.rb1
-rw-r--r--spec/bin/storage_check_spec.rb13
-rw-r--r--spec/controllers/admin/health_check_controller_spec.rb4
-rw-r--r--spec/controllers/groups/uploads_controller_spec.rb10
-rw-r--r--spec/controllers/health_controller_spec.rb42
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb18
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb3
-rw-r--r--spec/controllers/projects/pipelines_settings_controller_spec.rb15
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb247
-rw-r--r--spec/factories/notes.rb10
-rw-r--r--spec/factories/uploads.rb16
-rw-r--r--spec/features/admin/admin_health_check_spec.rb12
-rw-r--r--spec/features/markdown_spec.rb3
-rw-r--r--spec/features/merge_requests/versions_spec.rb105
-rw-r--r--spec/features/merge_requests/widget_spec.rb12
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb6
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb101
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb100
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb17
-rw-r--r--spec/javascripts/deploy_keys/components/action_btn_spec.js2
-rw-r--r--spec/javascripts/deploy_keys/components/app_spec.js14
-rw-r--r--spec/javascripts/job_spec.js2
-rw-r--r--spec/javascripts/lib/utils/datefix_spec.js2
-rw-r--r--spec/lib/backup/manager_spec.rb (renamed from spec/lib/gitlab/backup/manager_spec.rb)0
-rw-r--r--spec/lib/backup/repository_spec.rb69
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb8
-rw-r--r--spec/lib/banzai/filter/abstract_reference_filter_spec.rb38
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb47
-rw-r--r--spec/lib/banzai/filter/upload_link_filter_spec.rb30
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb4
-rw-r--r--spec/lib/gitlab/backup/repository_spec.rb117
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb4
-rw-r--r--spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb10
-rw-r--r--spec/lib/gitlab/git/remote_repository_spec.rb4
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/git/storage/checker_spec.rb132
-rw-r--r--spec/lib/gitlab/git/storage/circuit_breaker_spec.rb163
-rw-r--r--spec/lib/gitlab/git/storage/failure_info_spec.rb70
-rw-r--r--spec/lib/gitlab/git/storage/health_spec.rb2
-rw-r--r--spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb4
-rw-r--r--spec/lib/gitlab/git_spec.rb25
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb30
-rw-r--r--spec/lib/gitlab/storage_check/cli_spec.rb19
-rw-r--r--spec/lib/gitlab/storage_check/gitlab_caller_spec.rb46
-rw-r--r--spec/lib/gitlab/storage_check/option_parser_spec.rb31
-rw-r--r--spec/lib/gitlab/storage_check/response_spec.rb54
-rw-r--r--spec/models/application_setting_spec.rb15
-rw-r--r--spec/models/ci/build_spec.rb88
-rw-r--r--spec/models/ci/pipeline_spec.rb4
-rw-r--r--spec/models/diff_note_spec.rb29
-rw-r--r--spec/models/merge_request_spec.rb2
-rw-r--r--spec/models/personal_access_token_spec.rb25
-rw-r--r--spec/models/project_spec.rb19
-rw-r--r--spec/models/repository_spec.rb20
-rw-r--r--spec/models/user_spec.rb159
-rw-r--r--spec/policies/group_policy_spec.rb27
-rw-r--r--spec/requests/api/circuit_breakers_spec.rb2
-rw-r--r--spec/requests/api/protected_branches_spec.rb36
-rw-r--r--spec/requests/api/settings_spec.rb4
-rw-r--r--spec/services/ci/register_job_service_spec.rb83
-rw-r--r--spec/services/projects/fork_service_spec.rb353
-rw-r--r--spec/services/projects/update_service_spec.rb45
-rw-r--r--spec/services/system_note_service_spec.rb4
-rw-r--r--spec/spec_helper.rb12
-rw-r--r--spec/support/google_api/cloud_platform_helpers.rb2
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb240
-rw-r--r--spec/support/stored_repositories.rb21
-rw-r--r--spec/support/stub_configuration.rb2
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb2
-rw-r--r--spec/uploaders/namespace_file_uploader_spec.rb21
-rw-r--r--spec/views/projects/commit/show.html.haml_spec.rb22
-rw-r--r--spec/views/projects/merge_requests/_commits.html.haml_spec.rb4
-rw-r--r--spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb36
293 files changed, 4103 insertions, 2204 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4930b541ba2..01d4a546b97 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -598,6 +598,7 @@ merge request:
present time and never use past tense (has been/was). For example instead
of _prohibited this user from being saved due to the following errors:_ the
text should be _sorry, we could not create your account because:_
+1. Code should be written in [US English][us-english]
This is also the style used by linting tools such as
[RuboCop](https://github.com/bbatsov/rubocop),
@@ -663,6 +664,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
[testing]: doc/development/testing_guide/index.md
+[us-english]: https://en.wikipedia.org/wiki/American_English
[^1]: Please note that specs other than JavaScript specs are considered backend
code.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 46448c71b9d..cb6b534abe1 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.57.0
+0.59.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 509b0b618ad..4e32c7b1caf 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.10.0
+5.10.1
diff --git a/Gemfile.lock b/Gemfile.lock
index 379f2a4be53..6213167ae0b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -486,7 +486,6 @@ GEM
mini_mime (0.1.4)
mini_portile2 (2.3.0)
minitest (5.7.0)
- mmap2 (2.2.9)
mousetrap-rails (1.4.6)
multi_json (1.12.2)
multi_xml (0.6.0)
@@ -623,8 +622,7 @@ GEM
parser
unparser
procto (0.0.3)
- prometheus-client-mmap (0.7.0.beta39)
- mmap2 (~> 2.2, >= 2.2.9)
+ prometheus-client-mmap (0.7.0.beta43)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue
index 3f993213dd0..f9f2f9bf693 100644
--- a/app/assets/javascripts/deploy_keys/components/action_btn.vue
+++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue
@@ -32,7 +32,9 @@
doAction() {
this.isLoading = true;
- eventHub.$emit(`${this.type}.key`, this.deployKey);
+ eventHub.$emit(`${this.type}.key`, this.deployKey, () => {
+ this.isLoading = false;
+ });
},
},
computed: {
@@ -50,6 +52,9 @@
:disabled="isLoading"
@click="doAction">
{{ text }}
- <loading-icon v-if="isLoading" />
+ <loading-icon
+ v-if="isLoading"
+ :inline="true"
+ />
</button>
</template>
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index 54e13b79a4f..fe046449054 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -47,12 +47,15 @@
.then(() => this.fetchKeys())
.catch(() => new Flash('Error enabling deploy key'));
},
- disableKey(deployKey) {
+ disableKey(deployKey, callback) {
// eslint-disable-next-line no-alert
if (confirm('You are going to remove this deploy key. Are you sure?')) {
this.service.disableKey(deployKey.id)
.then(() => this.fetchKeys())
+ .then(callback)
.catch(() => new Flash('Error removing deploy key'));
+ } else {
+ callback();
}
},
},
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index 0863c3406bd..e0422057090 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
@@ -16,7 +16,8 @@ import './components/diff_note_avatars';
import './components/new_issue_for_discussion';
$(() => {
- const projectPath = document.querySelector('.merge-request').dataset.projectPath;
+ const projectPathHolder = document.querySelector('.merge-request') || document.querySelector('.commit-box');
+ const projectPath = projectPathHolder.dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js
index 6eae54f830b..96fe23640af 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js
+++ b/app/assets/javascripts/diff_notes/services/resolve.js
@@ -43,7 +43,7 @@ class ResolveServiceClass {
discussion.resolveAllNotes(resolvedBy);
}
- gl.mrWidget.checkStatus();
+ if (gl.mrWidget) gl.mrWidget.checkStatus();
discussion.updateHeadline(data);
})
.catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 24476d3d757..678af8f7b7a 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -525,13 +525,6 @@ import ProjectVariables from './project_variables';
case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels
initSettingsPanels();
-
- import(/* webpackChunkName: "ci-cd-settings" */ './projects/ci_cd_settings_bundle')
- .then(ciCdSettings => ciCdSettings.default())
- .catch((err) => {
- Flash(s__('ProjectSettings|Problem setting up the CI/CD settings JavaScript'));
- throw err;
- });
case 'groups:settings:ci_cd:show':
new ProjectVariables();
break;
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 0cd0c59a275..c76ce762b54 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -123,18 +123,23 @@ export default {
:title="group.fullName"
class="no-expand"
data-placement="top"
- >
- {{group.name}}
- </a>
+ >{{
+ // ending bracket must be by closing tag to prevent
+ // link hover text-decoration from over-extending
+ group.name
+ }}</a>
<span
v-if="group.permission"
- class="access-type"
+ class="user-access-role"
>
- {{s__('GroupsTreeRole|as')}} {{group.permission}}
+ {{group.permission}}
</span>
</div>
<div
- class="description">{{group.description}}</div>
+ v-if="group.description"
+ class="description">
+ {{group.description}}
+ </div>
</div>
<group-folder
v-if="group.isOpen && hasChildren"
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index aca9dec2a96..a21ce41e65e 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -5,7 +5,7 @@ import '../vue_shared/vue_resource_interceptor';
document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data');
- const initialData = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
+ const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.issuable-edit').on('click', (e) => {
e.preventDefault();
@@ -18,32 +18,9 @@ document.addEventListener('DOMContentLoaded', () => {
components: {
issuableApp,
},
- data() {
- return {
- ...initialData,
- };
- },
render(createElement) {
return createElement('issuable-app', {
- props: {
- canUpdate: this.canUpdate,
- canDestroy: this.canDestroy,
- endpoint: this.endpoint,
- issuableRef: this.issuableRef,
- initialTitleHtml: this.initialTitleHtml,
- initialTitleText: this.initialTitleText,
- initialDescriptionHtml: this.initialDescriptionHtml,
- initialDescriptionText: this.initialDescriptionText,
- issuableTemplates: this.issuableTemplates,
- markdownPreviewPath: this.markdownPreviewPath,
- markdownDocsPath: this.markdownDocsPath,
- projectPath: this.projectPath,
- projectNamespace: this.projectNamespace,
- updatedAt: this.updatedAt,
- updatedByName: this.updatedByName,
- updatedByPath: this.updatedByPath,
- initialTaskStatus: this.initialTaskStatus,
- },
+ props,
});
},
});
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index cf8fda9a4fa..85ea6330ee9 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -9,7 +9,7 @@ export default class Job {
this.state = null;
this.options = options || $('.js-build-options').data();
- this.pageUrl = this.options.pageUrl;
+ this.pagePath = this.options.pagePath;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
@@ -167,11 +167,11 @@ export default class Job {
getBuildTrace() {
return $.ajax({
- url: `${this.pageUrl}/trace.json`,
+ url: `${this.pagePath}/trace.json`,
data: { state: this.state },
})
.done((log) => {
- setCiStatusFavicon(`${this.pageUrl}/status.json`);
+ setCiStatusFavicon(`${this.pagePath}/status.json`);
if (log.state) {
this.state = log.state;
@@ -209,7 +209,7 @@ export default class Job {
}
if (log.status !== this.buildStatus) {
- gl.utils.visitUrl(this.pageUrl);
+ gl.utils.visitUrl(this.pagePath);
}
})
.fail(() => {
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 426a81a976d..d0578b230b1 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -35,8 +35,6 @@ window.dateFormat = dateFormat;
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
$timeagoEls.each((i, el) => {
- el.setAttribute('title', el.getAttribute('title'));
-
if (setTimeago) {
// Recreate with custom template
$(el).tooltip({
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 45fc6196be4..7fb45ed4d4b 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -86,7 +86,7 @@
<div class="note-actions">
<span
v-if="accessLevel"
- class="note-role note-role-access">{{accessLevel}}</span>
+ class="note-role user-access-role">{{accessLevel}}</span>
<div
v-if="canAddAwardEmoji"
class="note-actions-item">
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index ec77246e0d2..d250dd8d25b 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -8,10 +8,18 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
},
data() {
const notesDataset = document.getElementById('js-vue-notes').dataset;
+ const parsedUserData = JSON.parse(notesDataset.currentUserData);
+ const currentUserData = parsedUserData ? {
+ id: parsedUserData.id,
+ name: parsedUserData.name,
+ username: parsedUserData.username,
+ avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
+ path: parsedUserData.path,
+ } : {};
return {
noteableData: JSON.parse(notesDataset.noteableData),
- currentUserData: JSON.parse(notesDataset.currentUserData),
+ currentUserData,
notesData: {
lastFetchedAt: notesDataset.lastFetchedAt,
discussionsPath: notesDataset.discussionsPath,
diff --git a/app/assets/javascripts/projects/ci_cd_settings_bundle.js b/app/assets/javascripts/projects/ci_cd_settings_bundle.js
deleted file mode 100644
index 90e418f6771..00000000000
--- a/app/assets/javascripts/projects/ci_cd_settings_bundle.js
+++ /dev/null
@@ -1,19 +0,0 @@
-function updateAutoDevopsRadios(radioWrappers) {
- radioWrappers.forEach((radioWrapper) => {
- const radio = radioWrapper.querySelector('.js-auto-devops-enable-radio');
- const runPipelineCheckboxWrapper = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox-wrapper');
- const runPipelineCheckbox = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox');
-
- if (runPipelineCheckbox) {
- runPipelineCheckbox.checked = radio.checked;
- runPipelineCheckboxWrapper.classList.toggle('hide', !radio.checked);
- }
- });
-}
-
-export default function initCiCdSettings() {
- const radioWrappers = document.querySelectorAll('.js-auto-devops-enable-radio-wrapper');
- radioWrappers.forEach(radioWrapper =>
- radioWrapper.addEventListener('change', () => updateAutoDevopsRadios(radioWrappers)),
- );
-}
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 1274db2c4c8..9cb3edead86 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -1,3 +1,4 @@
+import Project from '~/project';
import SmartInterval from '~/smart_interval';
import Flash from '../flash';
import {
@@ -140,6 +141,7 @@ export default {
const el = document.createElement('div');
el.innerHTML = res.body;
document.body.appendChild(el);
+ Project.initRefSwitcher();
}
})
.catch(() => {
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index a42fab50db5..73524d5cf60 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -237,19 +237,6 @@ li.note {
}
}
-.browser-alert {
- padding: 10px;
- text-align: center;
- background: $error-bg;
- color: $white-light;
- font-weight: $gl-font-weight-bold;
-
- a {
- color: $white-light;
- text-decoration: underline;
- }
-}
-
.warning_message {
border-left: 4px solid $warning-message-border;
color: $warning-message-color;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index cd505bcaf6d..e6e6c4c3963 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -457,13 +457,11 @@ ul.indent-list {
ul.group-list-tree {
li.group-row {
- &.has-description {
- .title {
- line-height: inherit;
- }
+ &.has-description .title {
+ line-height: inherit;
}
- .title {
+ &:not(.has-description) .title {
line-height: $list-text-height;
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 6525b39d55c..4f99c27eff1 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -408,7 +408,6 @@ $location-icon-color: #e7e9ed;
* Notes
*/
$notes-light-color: $gl-text-color-secondary;
-$notes-role-color: $gl-text-color-secondary;
$note-disabled-comment-color: #b2b2b2;
$note-targe3-outside: #fffff0;
$note-targe3-inside: #ffffd3;
@@ -557,6 +556,7 @@ $jq-ui-default-color: #777;
/*
* Label
*/
+$label-padding: 7px;
$label-gray-bg: #f8fafc;
$label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1);
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 9b7dda9b648..f9a761e85fe 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -212,3 +212,15 @@
height: 50px;
}
}
+
+.user-access-role {
+ display: inline-block;
+ color: $gl-text-color-secondary;
+ font-size: 12px;
+ line-height: 20px;
+ margin: -5px 3px;
+ padding: 0 $label-padding;
+ border: 1px solid $border-color;
+ border-radius: $label-border-radius;
+ font-weight: $gl-font-weight-normal;
+}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 443f5500684..92abe82df4c 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -104,7 +104,7 @@
}
.color-label {
- padding: 3px 7px;
+ padding: 3px $label-padding;
border-radius: $label-border-radius;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index a6009ab328e..4d5613c292b 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -619,26 +619,17 @@ ul.notes {
}
.note-role {
+ margin: 0 3px;
+}
+
+.note-role-special {
position: relative;
display: inline-block;
- color: $notes-role-color;
+ color: $gl-text-color-secondary;
font-size: 12px;
- line-height: 20px;
- margin: 0 3px;
-
- &.note-role-access {
- padding: 0 7px;
- border: 1px solid $border-color;
- border-radius: $label-border-radius;
- }
-
- &.note-role-special {
- text-shadow: 0 0 15px $gl-text-color-inverted;
- }
+ text-shadow: 0 0 15px $gl-text-color-inverted;
}
-
-
/**
* Line note button on the side of diffs
*/
diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
index 65a17828feb..61247b280b3 100644
--- a/app/controllers/admin/health_check_controller.rb
+++ b/app/controllers/admin/health_check_controller.rb
@@ -5,7 +5,7 @@ class Admin::HealthCheckController < Admin::ApplicationController
end
def reset_storage_health
- Gitlab::Git::Storage::CircuitBreaker.reset_all!
+ Gitlab::Git::Storage::FailureInfo.reset_all!
redirect_to admin_health_check_path,
notice: _('Git storage health information has been reset')
end
diff --git a/app/controllers/concerns/renders_member_access.rb b/app/controllers/concerns/renders_member_access.rb
new file mode 100644
index 00000000000..d640378c24d
--- /dev/null
+++ b/app/controllers/concerns/renders_member_access.rb
@@ -0,0 +1,23 @@
+module RendersMemberAccess
+ def prepare_groups_for_rendering(groups)
+ preload_max_member_access_for_collection(Group, groups)
+
+ groups
+ end
+
+ def prepare_projects_for_rendering(projects)
+ preload_max_member_access_for_collection(Project, projects)
+
+ projects
+ end
+
+ private
+
+ def preload_max_member_access_for_collection(klass, collection)
+ return if !current_user || collection.blank?
+
+ method_name = "max_member_access_for_#{klass.name.underscore}_ids"
+
+ current_user.public_send(method_name, collection.ids) # rubocop:disable GitlabSecurity/PublicSend
+ end
+end
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index dec2e27335a..a6fb1f40001 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -1,4 +1,6 @@
module UploadsActions
+ include Gitlab::Utils::StrongMemoize
+
def create
link_to_file = UploadService.new(model, params[:file], uploader_class).execute
@@ -24,4 +26,25 @@ module UploadsActions
send_file uploader.file.path, disposition: disposition
end
+
+ private
+
+ def uploader
+ strong_memoize(:uploader) do
+ return if show_model.nil?
+
+ file_uploader = FileUploader.new(show_model, params[:secret])
+ file_uploader.retrieve_from_store!(params[:filename])
+
+ file_uploader
+ end
+ end
+
+ def image_or_video?
+ uploader && uploader.exists? && uploader.image_or_video?
+ end
+
+ def uploader_class
+ FileUploader
+ end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index d9884a47ec4..de9f8f9224a 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -1,5 +1,6 @@
class Dashboard::ProjectsController < Dashboard::ApplicationController
include ParamsBackwardCompatibility
+ include RendersMemberAccess
before_action :set_non_archived_param
before_action :default_sorting
@@ -45,10 +46,12 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
def load_projects(finder_params)
- ProjectsFinder
- .new(params: finder_params, current_user: current_user)
- .execute
- .includes(:route, :creator, namespace: [:route, :owner])
+ projects = ProjectsFinder
+ .new(params: finder_params, current_user: current_user)
+ .execute
+ .includes(:route, :creator, namespace: [:route, :owner])
+
+ prepare_projects_for_rendering(projects)
end
def load_events
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 762c6ebf3a3..c7273606a85 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,5 +1,6 @@
class Explore::ProjectsController < Explore::ApplicationController
include ParamsBackwardCompatibility
+ include RendersMemberAccess
before_action :set_non_archived_param
@@ -49,10 +50,12 @@ class Explore::ProjectsController < Explore::ApplicationController
private
def load_projects
- ProjectsFinder.new(current_user: current_user, params: params)
- .execute
- .includes(:route, namespace: :route)
- .page(params[:page])
- .without_count
+ projects = ProjectsFinder.new(current_user: current_user, params: params)
+ .execute
+ .includes(:route, namespace: :route)
+ .page(params[:page])
+ .without_count
+
+ prepare_projects_for_rendering(projects)
end
end
diff --git a/app/controllers/groups/uploads_controller.rb b/app/controllers/groups/uploads_controller.rb
new file mode 100644
index 00000000000..e6bd9806401
--- /dev/null
+++ b/app/controllers/groups/uploads_controller.rb
@@ -0,0 +1,35 @@
+class Groups::UploadsController < Groups::ApplicationController
+ include UploadsActions
+
+ skip_before_action :group, if: -> { action_name == 'show' && image_or_video? }
+
+ before_action :authorize_upload_file!, only: [:create]
+
+ private
+
+ def show_model
+ strong_memoize(:show_model) do
+ group_id = params[:group_id]
+
+ Group.find_by_full_path(group_id)
+ end
+ end
+
+ def authorize_upload_file!
+ render_404 unless can?(current_user, :upload_file, group)
+ end
+
+ def uploader
+ strong_memoize(:uploader) do
+ file_uploader = uploader_class.new(show_model, params[:secret])
+ file_uploader.retrieve_from_store!(params[:filename])
+ file_uploader
+ end
+ end
+
+ def uploader_class
+ NamespaceFileUploader
+ end
+
+ alias_method :model, :group
+end
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index 98c2aaa3526..a931b456a93 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -1,5 +1,5 @@
class HealthController < ActionController::Base
- protect_from_forgery with: :exception
+ protect_from_forgery with: :exception, except: :storage_check
include RequiresWhitelistedMonitoringClient
CHECKS = [
@@ -23,6 +23,15 @@ class HealthController < ActionController::Base
render_check_results(results)
end
+ def storage_check
+ results = Gitlab::Git::Storage::Checker.check_all
+
+ render json: {
+ check_interval: Gitlab::CurrentSettings.current_application_settings.circuitbreaker_check_interval,
+ results: results
+ }
+ end
+
private
def render_check_results(results)
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index 6d9873e38df..346eab4ba19 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -8,7 +8,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@personal_access_token = finder.build(personal_access_token_params)
if @personal_access_token.save
- flash[:personal_access_token] = @personal_access_token.token
+ PersonalAccessToken.redis_store!(current_user.id, @personal_access_token.token)
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
else
set_index_vars
@@ -43,5 +43,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
+
+ @new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 6ff96a3f295..2e7344b1cad 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -134,6 +134,23 @@ class Projects::CommitController < Projects::ApplicationController
@grouped_diff_discussions = commit.grouped_diff_discussions
@discussions = commit.discussions
+ if merge_request_iid = params[:merge_request_iid]
+ @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).find_by(iid: merge_request_iid)
+
+ if @merge_request
+ @new_diff_note_attrs.merge!(
+ noteable_type: 'MergeRequest',
+ noteable_id: @merge_request.id
+ )
+
+ merge_request_commit_notes = @merge_request.notes.where(commit_id: @commit.id).inc_relations_for_view
+ merge_request_commit_diff_discussions = merge_request_commit_notes.grouped_diff_discussions(@commit.diff_refs)
+ @grouped_diff_discussions.merge!(merge_request_commit_diff_discussions) do |line_code, left, right|
+ left + right
+ end
+ end
+ end
+
@notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes)
@notes = prepare_notes_for_rendering(@notes, @commit)
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 1269759fc2b..793ae03fb88 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -28,7 +28,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:task_num,
:title,
:discussion_locked,
-
label_ids: []
]
end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 9f966889995..fe8525a488c 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -4,6 +4,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
include RendersNotes
before_action :apply_diff_view_cookie!
+ before_action :commit
before_action :define_diff_vars
before_action :define_diff_comment_vars
@@ -20,18 +21,33 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
private
def define_diff_vars
+ @merge_request_diffs = @merge_request.merge_request_diffs.viewable.order_id_desc
+ @compare = commit || find_merge_request_diff_compare
+ return render_404 unless @compare
+
+ @diffs = @compare.diffs(diff_options)
+ end
+
+ def commit
+ return nil unless commit_id = params[:commit_id].presence
+ return nil unless @merge_request.all_commits.exists?(sha: commit_id)
+
+ @commit ||= @project.commit(commit_id)
+ end
+
+ def find_merge_request_diff_compare
@merge_request_diff =
- if params[:diff_id]
- @merge_request.merge_request_diffs.viewable.find(params[:diff_id])
+ if diff_id = params[:diff_id].presence
+ @merge_request.merge_request_diffs.viewable.find_by(id: diff_id)
else
@merge_request.merge_request_diff
end
- @merge_request_diffs = @merge_request.merge_request_diffs.viewable.order_id_desc
+ return unless @merge_request_diff
+
@comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
- if params[:start_sha].present?
- @start_sha = params[:start_sha]
+ if @start_sha = params[:start_sha].presence
@start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
unless @start_version
@@ -40,20 +56,18 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
end
end
- @compare =
- if @start_sha
- @merge_request_diff.compare_with(@start_sha)
- else
- @merge_request_diff
- end
-
- @diffs = @compare.diffs(diff_options)
+ if @start_sha
+ @merge_request_diff.compare_with(@start_sha)
+ else
+ @merge_request_diff
+ end
end
def define_diff_comment_vars
@new_diff_note_attrs = {
noteable_type: 'MergeRequest',
- noteable_id: @merge_request.id
+ noteable_id: @merge_request.id,
+ commit_id: @commit&.id
}
@diff_notes_disabled = false
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 37acd1c9787..e7b3b73024b 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -7,11 +7,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableCollections
skip_before_action :merge_request, only: [:index, :bulk_update]
-
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
-
before_action :set_issuables_index, only: [:index]
-
before_action :authenticate_user!, only: [:assign_related_issues]
def index
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index b890818c475..06ce7328fb5 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -29,7 +29,6 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
:runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_in_minutes, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
- :run_auto_devops_pipeline_implicit, :run_auto_devops_pipeline_explicit,
auto_devops_attributes: [:id, :domain, :enabled]
)
end
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 4d2fb17a19b..4685bbe80b4 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -8,31 +8,13 @@ class Projects::UploadsController < Projects::ApplicationController
private
- def uploader
- return @uploader if defined?(@uploader)
+ def show_model
+ strong_memoize(:show_model) do
+ namespace = params[:namespace_id]
+ id = params[:project_id]
- namespace = params[:namespace_id]
- id = params[:project_id]
-
- file_project = Project.find_by_full_path("#{namespace}/#{id}")
-
- if file_project.nil?
- @uploader = nil
- return
+ Project.find_by_full_path("#{namespace}/#{id}")
end
-
- @uploader = FileUploader.new(file_project, params[:secret])
- @uploader.retrieve_from_store!(params[:filename])
-
- @uploader
- end
-
- def image_or_video?
- uploader && uploader.exists? && uploader.image_or_video?
- end
-
- def uploader_class
- FileUploader
end
alias_method :model, :project
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 5fca31b4956..575ec5c20f0 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,5 +1,6 @@
class UsersController < ApplicationController
include RoutableActions
+ include RendersMemberAccess
skip_before_action :authenticate_user!
before_action :user, except: [:exists]
@@ -116,14 +117,20 @@ class UsersController < ApplicationController
@projects =
PersonalProjectsFinder.new(user).execute(current_user)
.page(params[:page])
+
+ prepare_projects_for_rendering(@projects)
end
def load_contributed_projects
@contributed_projects = contributed_projects.joined(user)
+
+ prepare_projects_for_rendering(@contributed_projects)
end
def load_groups
@groups = JoinedGroupsFinder.new(user).execute(current_user)
+
+ prepare_groups_for_rendering(@groups)
end
def load_snippets
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index dccde46fa33..b12ea760668 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -124,17 +124,6 @@ module ApplicationSettingsHelper
_('The number of attempts GitLab will make to access a storage.')
end
- def circuitbreaker_backoff_threshold_help_text
- _("The number of failures after which GitLab will start temporarily "\
- "disabling access to a storage shard on a host")
- end
-
- def circuitbreaker_failure_wait_time_help_text
- _("When access to a storage fails. GitLab will prevent access to the "\
- "storage for the time specified here. This allows the filesystem to "\
- "recover. Repositories on failing shards are temporarly unavailable")
- end
-
def circuitbreaker_failure_reset_time_help_text
_("The time in seconds GitLab will keep failure information. When no "\
"failures occur during this time, information about the mount is reset.")
@@ -145,6 +134,11 @@ module ApplicationSettingsHelper
"timeout error will be raised.")
end
+ def circuitbreaker_check_interval_help_text
+ _("The time in seconds between storage checks. When a previous check did "\
+ "complete yet, GitLab will skip a check.")
+ end
+
def visible_attributes
[
:admin_notification_email,
@@ -154,10 +148,9 @@ module ApplicationSettingsHelper
:akismet_enabled,
:auto_devops_enabled,
:circuitbreaker_access_retries,
- :circuitbreaker_backoff_threshold,
+ :circuitbreaker_check_interval,
:circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_reset_time,
- :circuitbreaker_failure_wait_time,
:circuitbreaker_storage_timeout,
:clientside_sentry_dsn,
:clientside_sentry_enabled,
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index ec6194d204f..f4310ca2f06 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -8,22 +8,6 @@ module AutoDevopsHelper
!project.ci_service
end
- def show_run_auto_devops_pipeline_checkbox_for_instance_setting?(project)
- return false if project.repository.gitlab_ci_yml
-
- if project&.auto_devops&.enabled.present?
- !project.auto_devops.enabled && current_application_settings.auto_devops_enabled?
- else
- current_application_settings.auto_devops_enabled?
- end
- end
-
- def show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(project)
- return false if project.repository.gitlab_ci_yml
-
- !project.auto_devops_enabled?
- end
-
def auto_devops_warning_message(project)
missing_domain = !project.auto_devops&.has_domain?
missing_service = !project.deployment_platform&.active?
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index aa3a9a055a0..4ec63fdaffc 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -20,8 +20,7 @@ module BuildsHelper
def javascript_build_options
{
- page_url: project_job_url(@project, @build),
- build_url: project_job_url(@project, @build, :json),
+ page_path: project_job_path(@project, @build),
build_status: @build.status,
build_stage: @build.stage,
log_state: ''
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index f68e2cd3afa..2d304f7eb91 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -228,4 +228,12 @@ module CommitsHelper
[commits, 0]
end
end
+
+ def commit_path(project, commit, merge_request: nil)
+ if merge_request&.persisted?
+ diffs_project_merge_request_path(project, merge_request, commit_id: commit.id)
+ else
+ project_commit_path(project, commit)
+ end
+ end
end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 1e4be2d4bcf..f78d41a0448 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -86,6 +86,8 @@ module MarkupHelper
return '' unless text.present?
context[:project] ||= @project
+ context[:group] ||= @group
+
html = markdown_unsafe(text, context)
prepare_for_rendering(html, context)
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 5b2c58d193d..ce57422f45d 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -101,6 +101,30 @@ module MergeRequestsHelper
}.merge(merge_params_ee(merge_request))
end
+ def tab_link_for(merge_request, tab, options = {}, &block)
+ data_attrs = {
+ action: tab.to_s,
+ target: "##{tab}",
+ toggle: options.fetch(:force_link, false) ? '' : 'tab'
+ }
+
+ url = case tab
+ when :show
+ data_attrs[:target] = '#notes'
+ method(:project_merge_request_path)
+ when :commits
+ method(:commits_project_merge_request_path)
+ when :pipelines
+ method(:pipelines_project_merge_request_path)
+ when :diffs
+ method(:diffs_project_merge_request_path)
+ else
+ raise "Cannot create tab #{tab}."
+ end
+
+ link_to(url[merge_request.project, merge_request], data: data_attrs, &block)
+ end
+
def merge_params_ee(merge_request)
{}
end
diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb
index 4d2180f7eee..b76c1228220 100644
--- a/app/helpers/storage_health_helper.rb
+++ b/app/helpers/storage_health_helper.rb
@@ -18,16 +18,12 @@ module StorageHealthHelper
current_failures = circuit_breaker.failure_count
translation_params = { number_of_failures: current_failures,
- maximum_failures: maximum_failures,
- number_of_seconds: circuit_breaker.failure_wait_time }
+ maximum_failures: maximum_failures }
if circuit_breaker.circuit_broken?
s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
"retry automatically. Reset storage information when the problem is "\
"resolved.") % translation_params
- elsif circuit_breaker.backing_off?
- _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
- "block access for %{number_of_seconds} seconds.") % translation_params
else
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
"allow access on the next attempt.") % translation_params
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 3117c98c846..253e213af81 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -153,11 +153,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { greater_than_or_equal_to: 0 }
- validates :circuitbreaker_backoff_threshold,
- :circuitbreaker_failure_count_threshold,
- :circuitbreaker_failure_wait_time,
+ validates :circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_reset_time,
:circuitbreaker_storage_timeout,
+ :circuitbreaker_check_interval,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
@@ -165,13 +164,6 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 }
- validates_each :circuitbreaker_backoff_threshold do |record, attr, value|
- if value.to_i >= record.circuitbreaker_failure_count_threshold
- record.errors.add(attr, _("The circuitbreaker backoff threshold should be "\
- "lower than the failure count threshold"))
- end
- end
-
validates :gitaly_timeout_default,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index d2402b55184..85960f1b6bb 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -6,6 +6,8 @@ module Ci
include Presentable
include Importable
+ MissingDependenciesError = Class.new(StandardError)
+
belongs_to :runner
belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User'
@@ -139,6 +141,10 @@ module Ci
Ci::Build.retry(build, build.user)
end
end
+
+ before_transition any => [:running] do |build|
+ build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
+ end
end
def detailed_status(current_user)
@@ -478,6 +484,20 @@ module Ci
options[:dependencies]&.empty?
end
+ def validates_dependencies!
+ dependencies.each do |dependency|
+ raise MissingDependenciesError unless dependency.valid_dependency?
+ end
+ end
+
+ def valid_dependency?
+ return false unless complete?
+ return false if artifacts_expired?
+ return false if erased?
+
+ true
+ end
+
def hide_secrets(trace)
return unless trace
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6b28d290f99..307e4fcedfe 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
class Commit
extend ActiveModel::Naming
extend Gitlab::Cache::RequestCache
@@ -25,7 +26,7 @@ class Commit
DIFF_HARD_LIMIT_FILES = 1000
DIFF_HARD_LIMIT_LINES = 50000
- MIN_SHA_LENGTH = 7
+ MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
def banzai_render_context(field)
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index ee21ed8e420..c0263c0b4e2 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -43,7 +43,8 @@ class CommitStatus < ActiveRecord::Base
script_failure: 1,
api_failure: 2,
stuck_or_timeout_failure: 3,
- runner_system_failure: 4
+ runner_system_failure: 4,
+ missing_dependency_failure: 5
}
##
diff --git a/app/models/concerns/bulk_member_access_load.rb b/app/models/concerns/bulk_member_access_load.rb
new file mode 100644
index 00000000000..984c4f53bf7
--- /dev/null
+++ b/app/models/concerns/bulk_member_access_load.rb
@@ -0,0 +1,46 @@
+# Returns and caches in thread max member access for a resource
+#
+module BulkMemberAccessLoad
+ extend ActiveSupport::Concern
+
+ included do
+ # Determine the maximum access level for a group of resources in bulk.
+ #
+ # Returns a Hash mapping resource ID -> maximum access level.
+ def max_member_access_for_resource_ids(resource_klass, resource_ids, memoization_index = self.id, &block)
+ raise 'Block is mandatory' unless block_given?
+
+ resource_ids = resource_ids.uniq
+ key = max_member_access_for_resource_key(resource_klass, memoization_index)
+ access = {}
+
+ if RequestStore.active?
+ RequestStore.store[key] ||= {}
+ access = RequestStore.store[key]
+ end
+
+ # Look up only the IDs we need
+ resource_ids = resource_ids - access.keys
+
+ return access if resource_ids.empty?
+
+ resource_access = yield(resource_ids)
+
+ access.merge!(resource_access)
+
+ missing_resource_ids = resource_ids - resource_access.keys
+
+ missing_resource_ids.each do |resource_id|
+ access[resource_id] = Gitlab::Access::NO_ACCESS
+ end
+
+ access
+ end
+
+ private
+
+ def max_member_access_for_resource_key(klass, memoization_index)
+ "max_member_access_for_#{klass.name.underscore.pluralize}:#{memoization_index}"
+ end
+ end
+end
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index f5cbb3becad..4b4d519f3df 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -32,6 +32,10 @@ module DiscussionOnDiff
first_note.position.new_path
end
+ def on_merge_request_commit?
+ for_merge_request? && commit_id.present?
+ end
+
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines
diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb
index 6eba87da1a1..4a65738214b 100644
--- a/app/models/diff_discussion.rb
+++ b/app/models/diff_discussion.rb
@@ -24,7 +24,11 @@ class DiffDiscussion < Discussion
return unless for_merge_request?
return {} if active?
- noteable.version_params_for(position.diff_refs)
+ if on_merge_request_commit?
+ { commit_id: commit_id }
+ else
+ noteable.version_params_for(position.diff_refs)
+ end
end
def reply_attributes
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index ae5f138a920..b53d44cda95 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -17,6 +17,7 @@ class DiffNote < Note
validates :noteable_type, inclusion: { in: NOTEABLE_TYPES }
validate :positions_complete
validate :verify_supported
+ validate :diff_refs_match_commit, if: :for_commit?
before_validation :set_original_position, on: :create
before_validation :update_position, on: :create, if: :on_text?
@@ -135,6 +136,12 @@ class DiffNote < Note
errors.add(:position, "is invalid")
end
+ def diff_refs_match_commit
+ return if self.original_position.diff_refs == self.commit.diff_refs
+
+ errors.add(:commit_id, 'does not match the diff refs')
+ end
+
def keep_around_commits
project.repository.keep_around(self.original_position.base_sha)
project.repository.keep_around(self.original_position.start_sha)
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 437df923d2d..92482a1a875 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -11,6 +11,7 @@ class Discussion
:author,
:noteable,
+ :commit_id,
:for_commit?,
:for_merge_request?,
diff --git a/app/models/epic.rb b/app/models/epic.rb
index 62898a02e2d..286b855de3f 100644
--- a/app/models/epic.rb
+++ b/app/models/epic.rb
@@ -1,7 +1,11 @@
# Placeholder class for model that is implemented in EE
-# It will reserve (ee#3853) '&' as a reference prefix, but the table does not exists in CE
+# It reserves '&' as a reference prefix, but the table does not exists in CE
class Epic < ActiveRecord::Base
- # TODO: this will be implemented as part of #3853
- def to_reference
+ def self.reference_prefix
+ '&'
+ end
+
+ def self.reference_prefix_escaped
+ '&amp;'
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 505e943e464..fddace03387 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -298,6 +298,10 @@ class Group < Namespace
end
end
+ def hashed_storage?(_feature)
+ false
+ end
+
private
def update_two_factor_requirement
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 949d42f865c..422f138c4ea 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -649,6 +649,7 @@ class MergeRequest < ActiveRecord::Base
.to_sql
Note.from("(#{union}) #{Note.table_name}")
+ .includes(:noteable)
end
alias_method :discussion_notes, :related_notes
@@ -925,21 +926,27 @@ class MergeRequest < ActiveRecord::Base
.order(id: :desc)
end
- # Note that this could also return SHA from now dangling commits
- #
- def all_commit_shas
- return commit_shas unless persisted?
-
- diffs_relation = merge_request_diffs
-
+ def all_commits
# MySQL doesn't support LIMIT in a subquery.
- diffs_relation = diffs_relation.recent if Gitlab::Database.postgresql?
+ diffs_relation = if Gitlab::Database.postgresql?
+ merge_request_diffs.recent
+ else
+ merge_request_diffs
+ end
MergeRequestDiffCommit
.where(merge_request_diff: diffs_relation)
.limit(10_000)
- .pluck('sha')
- .uniq
+ end
+
+ # Note that this could also return SHA from now dangling commits
+ #
+ def all_commit_shas
+ @all_commit_shas ||= begin
+ return commit_shas unless persisted?
+
+ all_commits.pluck(:sha).uniq
+ end
end
def merge_commit
diff --git a/app/models/note.rb b/app/models/note.rb
index 733bbbc013f..c4c2ab8e67d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -230,16 +230,18 @@ class Note < ActiveRecord::Base
for_personal_snippet?
end
+ def commit
+ @commit ||= project.commit(commit_id) if commit_id.present?
+ end
+
# override to return commits, which are not active record
def noteable
- if for_commit?
- @commit ||= project.commit(commit_id)
- else
- super
- end
- # Temp fix to prevent app crash
- # if note commit id doesn't exist
+ return commit if for_commit?
+
+ super
rescue
+ # Temp fix to prevent app crash
+ # if note commit id doesn't exist
nil
end
@@ -401,6 +403,10 @@ class Note < ActiveRecord::Base
noteable_object&.touch
end
+ def banzai_render_context(field)
+ super.merge(noteable: noteable)
+ end
+
private
def keep_around_commit
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index cfcb03138b7..063dc521324 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -3,6 +3,8 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :token
+ REDIS_EXPIRY_TIME = 3.minutes
+
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user
@@ -27,6 +29,21 @@ class PersonalAccessToken < ActiveRecord::Base
!revoked? && !expired?
end
+ def self.redis_getdel(user_id)
+ Gitlab::Redis::SharedState.with do |redis|
+ token = redis.get(redis_shared_state_key(user_id))
+ redis.del(redis_shared_state_key(user_id))
+ token
+ end
+ end
+
+ def self.redis_store!(user_id, token)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(redis_shared_state_key(user_id), token, ex: REDIS_EXPIRY_TIME)
+ token
+ end
+ end
+
protected
def validate_scopes
@@ -38,4 +55,8 @@ class PersonalAccessToken < ActiveRecord::Base
def set_default_scopes
self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
end
+
+ def self.redis_shared_state_key(user_id)
+ "gitlab:personal_access_token:#{user_id}"
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 41657c171e2..6ae15a0a50f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -227,7 +227,6 @@ class Project < ActiveRecord::Base
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
- delegate :empty_repo?, to: :repository
# Validations
validates :creator, presence: true, on: :create
@@ -499,6 +498,10 @@ class Project < ActiveRecord::Base
auto_devops&.enabled.nil? && !current_application_settings.auto_devops_enabled?
end
+ def empty_repo?
+ repository.empty?
+ end
+
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage].try(:[], 'path')
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 1d35426050e..c679758973a 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -1,4 +1,6 @@
class ProjectTeam
+ include BulkMemberAccessLoad
+
attr_accessor :project
def initialize(project)
@@ -157,39 +159,16 @@ class ProjectTeam
#
# Returns a Hash mapping user ID -> maximum access level.
def max_member_access_for_user_ids(user_ids)
- user_ids = user_ids.uniq
- key = "max_member_access:#{project.id}"
-
- access = {}
-
- if RequestStore.active?
- RequestStore.store[key] ||= {}
- access = RequestStore.store[key]
+ max_member_access_for_resource_ids(User, user_ids, project.id) do |user_ids|
+ project.project_authorizations
+ .where(user: user_ids)
+ .group(:user_id)
+ .maximum(:access_level)
end
-
- # Look up only the IDs we need
- user_ids = user_ids - access.keys
-
- return access if user_ids.empty?
-
- users_access = project.project_authorizations
- .where(user: user_ids)
- .group(:user_id)
- .maximum(:access_level)
-
- access.merge!(users_access)
-
- missing_user_ids = user_ids - users_access.keys
-
- missing_user_ids.each do |user_id|
- access[user_id] = Gitlab::Access::NO_ACCESS
- end
-
- access
end
def max_member_access(user_id)
- max_member_access_for_user_ids([user_id])[user_id] || Gitlab::Access::NO_ACCESS
+ max_member_access_for_user_ids([user_id])[user_id]
end
private
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 82af299ec5e..751306188a0 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -37,7 +37,7 @@ class Repository
issue_template_names merge_request_template_names).freeze
# Methods that use cache_method but only memoize the value
- MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze
+ MEMOIZED_CACHED_METHODS = %i(license).freeze
# Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
@@ -497,7 +497,11 @@ class Repository
end
cache_method :exists?
- delegate :empty?, to: :raw_repository
+ def empty?
+ return true unless exists?
+
+ !has_visible_content?
+ end
cache_method :empty?
# The size of this repository in megabytes.
@@ -944,13 +948,8 @@ class Repository
end
end
- def empty_repo?
- !exists? || !has_visible_content?
- end
- cache_method :empty_repo?, memoize_only: true
-
def search_files_by_content(query, ref)
- return [] if empty_repo? || query.blank?
+ return [] if empty? || query.blank?
offset = 2
args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
@@ -959,7 +958,7 @@ class Repository
end
def search_files_by_name(query, ref)
- return [] if empty_repo? || query.blank?
+ return [] if empty? || query.blank?
args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
diff --git a/app/models/user.rb b/app/models/user.rb
index 38ee4ed50c1..af1c36d9c93 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -17,6 +17,7 @@ class User < ActiveRecord::Base
include FeatureGate
include CreatedAtFilterable
include IgnorableColumn
+ include BulkMemberAccessLoad
DEFAULT_NOTIFICATION_LEVEL = :participating
@@ -1144,6 +1145,34 @@ class User < ActiveRecord::Base
super
end
+ # Determine the maximum access level for a group of projects in bulk.
+ #
+ # Returns a Hash mapping project ID -> maximum access level.
+ def max_member_access_for_project_ids(project_ids)
+ max_member_access_for_resource_ids(Project, project_ids) do |project_ids|
+ project_authorizations.where(project: project_ids)
+ .group(:project_id)
+ .maximum(:access_level)
+ end
+ end
+
+ def max_member_access_for_project(project_id)
+ max_member_access_for_project_ids([project_id])[project_id]
+ end
+
+ # Determine the maximum access level for a group of groups in bulk.
+ #
+ # Returns a Hash mapping project ID -> maximum access level.
+ def max_member_access_for_group_ids(group_ids)
+ max_member_access_for_resource_ids(Group, group_ids) do |group_ids|
+ group_members.where(source: group_ids).group(:source_id).maximum(:access_level)
+ end
+ end
+
+ def max_member_access_for_group(group_id)
+ max_member_access_for_group_ids([group_id])[group_id]
+ end
+
protected
# override, from Devise::Validatable
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index a2518bc1080..d2d45e402b0 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -30,7 +30,12 @@ class GroupPolicy < BasePolicy
rule { public_group } .enable :read_group
rule { logged_in_viewable }.enable :read_group
- rule { guest } .enable :read_group
+
+ rule { guest }.policy do
+ enable :read_group
+ enable :upload_file
+ end
+
rule { admin } .enable :read_group
rule { has_projects } .enable :read_group
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 2ef76e03031..c8b6450c9b5 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -54,6 +54,9 @@ module Ci
# we still have to return 409 in the end,
# to make sure that this is properly handled by runner.
valid = false
+ rescue Ci::Build::MissingDependenciesError
+ build.drop!(:missing_dependency_failure)
+ valid = false
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 434dda89db0..9f05535d4d4 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -6,7 +6,7 @@ module MergeRequests
@oldrev, @newrev = oldrev, newrev
@branch_name = Gitlab::Git.ref_name(ref)
- find_new_commits
+ Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits))
# Be sure to close outstanding MRs before reloading them to avoid generating an
# empty diff during a manual merge
close_merge_requests
diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb
index 6b3939aeba5..236e9fe8c44 100644
--- a/app/services/metrics_service.rb
+++ b/app/services/metrics_service.rb
@@ -20,7 +20,7 @@ class MetricsService
end
def metrics_text
- "#{health_metrics_text}#{prometheus_metrics_text}"
+ prometheus_metrics_text.concat(health_metrics_text)
end
private
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index eb5cce5ab98..03be7039b2a 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -1,6 +1,24 @@
module Projects
class ForkService < BaseService
- def execute
+ def execute(fork_to_project = nil)
+ if fork_to_project
+ link_existing_project(fork_to_project)
+ else
+ fork_new_project
+ end
+ end
+
+ private
+
+ def link_existing_project(fork_to_project)
+ return if fork_to_project.forked?
+
+ link_fork_network(fork_to_project)
+
+ fork_to_project
+ end
+
+ def fork_new_project
new_params = {
forked_from_project_id: @project.id,
visibility_level: allowed_visibility_level,
@@ -21,15 +39,11 @@ module Projects
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update_attributes(builds_access_level: builds_access_level)
- refresh_forks_count
-
link_fork_network(new_project)
new_project
end
- private
-
def fork_network
if @project.fork_network
@project.fork_network
@@ -43,9 +57,17 @@ module Projects
end
end
- def link_fork_network(new_project)
- fork_network.fork_network_members.create(project: new_project,
+ def link_fork_network(fork_to_project)
+ fork_network.fork_network_members.create(project: fork_to_project,
forked_from_project: @project)
+
+ # TODO: remove this when ForkedProjectLink model is removed
+ unless fork_to_project.forked_project_link
+ fork_to_project.create_forked_project_link(forked_to_project: fork_to_project,
+ forked_from_project: @project)
+ end
+
+ refresh_forks_count
end
def refresh_forks_count
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 72eecc61c96..ff4c73c886e 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -15,7 +15,7 @@ module Projects
return error("Could not set the default branch") unless project.change_head(params[:default_branch])
end
- if project.update_attributes(update_params)
+ if project.update_attributes(params.except(:default_branch))
if project.previous_changes.include?('path')
project.rename_repo
else
@@ -32,15 +32,13 @@ module Projects
end
def run_auto_devops_pipeline?
- params.dig(:run_auto_devops_pipeline_explicit) == 'true' || params.dig(:run_auto_devops_pipeline_implicit) == 'true'
+ return false if project.repository.gitlab_ci_yml || !project.auto_devops.previous_changes.include?('enabled')
+
+ project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && current_application_settings.auto_devops_enabled?)
end
private
- def update_params
- params.except(:default_branch, :run_auto_devops_pipeline_explicit, :run_auto_devops_pipeline_implicit)
- end
-
def renaming_project_with_container_registry_tags?
new_path = params[:path]
diff --git a/app/services/protected_branches/access_level_params.rb b/app/services/protected_branches/access_level_params.rb
new file mode 100644
index 00000000000..253ae8b0124
--- /dev/null
+++ b/app/services/protected_branches/access_level_params.rb
@@ -0,0 +1,33 @@
+module ProtectedBranches
+ class AccessLevelParams
+ attr_reader :type, :params
+
+ def initialize(type, params)
+ @type = type
+ @params = params_with_default(params)
+ end
+
+ def access_levels
+ ce_style_access_level
+ end
+
+ private
+
+ def params_with_default(params)
+ params[:"#{type}_access_level"] ||= Gitlab::Access::MASTER if use_default_access_level?(params)
+ params
+ end
+
+ def use_default_access_level?(params)
+ true
+ end
+
+ def ce_style_access_level
+ access_level = params[:"#{type}_access_level"]
+
+ return [] unless access_level
+
+ [{ access_level: access_level }]
+ end
+ end
+end
diff --git a/app/services/protected_branches/api_service.rb b/app/services/protected_branches/api_service.rb
new file mode 100644
index 00000000000..4b40200644b
--- /dev/null
+++ b/app/services/protected_branches/api_service.rb
@@ -0,0 +1,24 @@
+module ProtectedBranches
+ class ApiService < BaseService
+ def create
+ @push_params = AccessLevelParams.new(:push, params)
+ @merge_params = AccessLevelParams.new(:merge, params)
+
+ verify_params!
+
+ protected_branch_params = {
+ name: params[:name],
+ push_access_levels_attributes: @push_params.access_levels,
+ merge_access_levels_attributes: @merge_params.access_levels
+ }
+
+ ::ProtectedBranches::CreateService.new(@project, @current_user, protected_branch_params).execute
+ end
+
+ private
+
+ def verify_params!
+ # EE-only
+ end
+ end
+end
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 71658df5b41..0b591e3bbbb 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -29,11 +29,11 @@ class FileUploader < GitlabUploader
# model - Object that responds to `full_path` and `disk_path`
#
# Returns a String without a trailing slash
- def self.dynamic_path_segment(project)
- if project.hashed_storage?(:attachments)
- dynamic_path_builder(project.disk_path)
+ def self.dynamic_path_segment(model)
+ if model.hashed_storage?(:attachments)
+ dynamic_path_builder(model.disk_path)
else
- dynamic_path_builder(project.full_path)
+ dynamic_path_builder(model.full_path)
end
end
diff --git a/app/uploaders/namespace_file_uploader.rb b/app/uploaders/namespace_file_uploader.rb
new file mode 100644
index 00000000000..672126e9ec2
--- /dev/null
+++ b/app/uploaders/namespace_file_uploader.rb
@@ -0,0 +1,15 @@
+class NamespaceFileUploader < FileUploader
+ def self.base_dir
+ File.join(root_dir, '-', 'system', 'namespace')
+ end
+
+ def self.dynamic_path_segment(model)
+ dynamic_path_builder(model.id.to_s)
+ end
+
+ private
+
+ def secure_url
+ File.join('/uploads', @secret, file.filename)
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index a9d0503bc73..3e2dbb07a6c 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -546,6 +546,12 @@
%fieldset
%legend Git Storage Circuitbreaker settings
.form-group
+ = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :circuitbreaker_check_interval, class: 'form-control'
+ .help-block
+ = circuitbreaker_check_interval_help_text
+ .form-group
= f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_access_retries, class: 'form-control'
@@ -558,18 +564,6 @@
.help-block
= circuitbreaker_storage_timeout_help_text
.form-group
- = f.label :circuitbreaker_backoff_threshold, _('Number of failures before backing off'), class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :circuitbreaker_backoff_threshold, class: 'form-control'
- .help-block
- = circuitbreaker_backoff_threshold_help_text
- .form-group
- = f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :circuitbreaker_failure_wait_time, class: 'form-control'
- .help-block
- = circuitbreaker_failure_wait_time_help_text
- .form-group
= f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index 0ebd7c01bab..9e0e908e656 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -1 +1 @@
-= render 'shared/projects/list', projects: @projects, ci: true
+= render 'shared/projects/list', projects: @projects, ci: true, user: current_user
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 0f03163a2e8..205320ed87c 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -32,9 +32,17 @@
- elsif discussion.diff_discussion?
on
= conditional_link_to url.present?, url do
- - unless discussion.active?
- an old version of
- the diff
+ - if discussion.on_merge_request_commit?
+ - unless discussion.active?
+ an outdated change in
+ commit
+
+ %span.commit-sha= Commit.truncate_sha(discussion.commit_id)
+ - else
+ - unless discussion.active?
+ an old version of
+ the diff
+
= time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
= render "discussions/headline", discussion: discussion
diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml
index 708fbc27f55..67f2f897137 100644
--- a/app/views/explore/projects/_projects.html.haml
+++ b/app/views/explore/projects/_projects.html.haml
@@ -1 +1 @@
-= render 'shared/projects/list', projects: projects
+= render 'shared/projects/list', projects: projects, user: current_user
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 1fd301d6850..25ed610466a 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -2,6 +2,7 @@
- if defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"
.content-wrapper.page-with-new-nav
+ = render 'shared/outdated_browser'
.mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 30ae385f62f..52587760ba4 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -13,7 +13,14 @@
.location-badge= label
.search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } }
- = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }, aria: { label: 'Search' }
+ = search_field_tag 'search', nil, placeholder: 'Search',
+ class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options',
+ spellcheck: false,
+ tabindex: '1',
+ autocomplete: 'off',
+ data: { issues_path: issues_dashboard_path,
+ mr_path: merge_requests_dashboard_path },
+ aria: { label: 'Search' }
%button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } }
.dropdown-menu.dropdown-select
= dropdown_content do
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 08bd6fc311e..bfbfeee7c4b 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -4,4 +4,10 @@
- nav "group"
- @left_sidebar = true
+- content_for :page_specific_javascripts do
+ - if current_user
+ -# haml-lint:disable InlineJavaScript
+ :javascript
+ window.uploads_path = "#{group_uploads_path(@group)}";
+
= render template: "layouts/application"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index e2407f6a428..99e7f3b568d 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -75,5 +75,3 @@
%span.sr-only Toggle navigation
= sprite_icon('more', size: 12, css_class: 'more-icon js-navbar-toggle-right')
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
-
-= render 'shared/outdated_browser'
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 26c2e4c5936..f445e5a2417 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -15,14 +15,13 @@
They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.
.col-lg-8
-
- - if flash[:personal_access_token]
+ - if @new_personal_access_token
.created-personal-access-token-container
%h5.prepend-top-0
Your New Personal Access Token
.form-group
- = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
- = clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
+ = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
+ = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 2cd5d0c60ea..c5e3a7945bd 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -2,7 +2,7 @@
- if defined?(@merge_request) && @merge_request.discussion_locked?
.issuable-note-warning
- = icon('lock', class: 'icon')
+ = sprite_icon('lock', size: 16, css_class: 'icon')
%span
= _('This merge request is locked.')
= _('Only project members can comment.')
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index c1842527480..86510b8ab93 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -14,7 +14,7 @@
%td.branch-commit
- if can?(current_user, :read_build, job)
- = link_to project_job_url(job.project, job) do
+ = link_to project_job_path(job.project, job) do
%span.build-link ##{job.id}
- else
%span.build-link ##{job.id}
diff --git a/app/views/projects/clusters/_advanced_settings.html.haml b/app/views/projects/clusters/_advanced_settings.html.haml
index 2b3095eb94b..7032b892029 100644
--- a/app/views/projects/clusters/_advanced_settings.html.haml
+++ b/app/views/projects/clusters/_advanced_settings.html.haml
@@ -2,14 +2,14 @@
- if @cluster.managed?
.append-bottom-20
%label.append-bottom-10
- = s_('ClusterIntegration|Google Container Engine')
+ = s_('ClusterIntegration|Google Kubernetes Engine')
%p
- - link_gke = link_to(s_('ClusterIntegration|Google Container Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer')
+ - link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Manage your cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke }
.well.form-group
%label.text-danger
= s_('ClusterIntegration|Remove cluster integration')
%p
- = s_('ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Container Engine.')
- = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: "Are you sure you want to remove cluster integration from this project? This will not delete your cluster on Google Container Engine"})
+ = s_('ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine.')
+ = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: "Are you sure you want to remove cluster integration from this project? This will not delete your cluster on Google Kubernetes Engine"})
diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml
index a1cc66eac92..76a66fb92a2 100644
--- a/app/views/projects/clusters/_banner.html.haml
+++ b/app/views/projects/clusters/_banner.html.haml
@@ -2,14 +2,14 @@
.settings-content
.hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' }
- = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine')
+ = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine')
%p.js-error-reason
.hidden.js-cluster-creating.alert.alert-info.alert-block.append-bottom-10{ role: 'alert' }
- = s_('ClusterIntegration|Cluster is being created on Google Container Engine...')
+ = s_('ClusterIntegration|Cluster is being created on Google Kubernetes Engine...')
.hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' }
- = s_('ClusterIntegration|Cluster was successfully created on Google Container Engine. Refresh the page to see cluster\'s details')
+ = s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details')
%p
- if @cluster.enabled?
diff --git a/app/views/projects/clusters/_dropdown.html.haml b/app/views/projects/clusters/_dropdown.html.haml
index 39188c7ca27..e36dd900f8d 100644
--- a/app/views/projects/clusters/_dropdown.html.haml
+++ b/app/views/projects/clusters/_dropdown.html.haml
@@ -7,6 +7,6 @@
= icon('chevron-down')
%ul.dropdown-menu.clusters-dropdown-menu.dropdown-menu-full-width
%li
- = link_to(s_('ClusterIntegration|Create cluster on Google Container Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project))
+ = link_to(s_('ClusterIntegration|Create cluster on Google Kubernetes Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project))
%li
= link_to(s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project))
diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/projects/clusters/gcp/_header.html.haml
index cddb53c2688..f23d5b80e4f 100644
--- a/app/views/projects/clusters/gcp/_header.html.haml
+++ b/app/views/projects/clusters/gcp/_header.html.haml
@@ -4,11 +4,11 @@
= s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:')
%ul
%li
- - link_to_container_engine = link_to(s_('ClusterIntegration|access to Google Container Engine'), 'https://console.cloud.google.com', target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|Your account must have %{link_to_container_engine}').html_safe % { link_to_container_engine: link_to_container_engine }
+ - link_to_kubernetes_engine = link_to(s_('ClusterIntegration|access to Google Kubernetes Engine'), 'https://console.cloud.google.com', target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Your account must have %{link_to_kubernetes_engine}').html_safe % { link_to_kubernetes_engine: link_to_kubernetes_engine }
%li
- - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/container-engine/docs/quickstart', target: '_blank', rel: 'noopener noreferrer')
+ - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements }
%li
- - link_to_container_project = link_to(s_('ClusterIntegration|Google Container Engine project'), target: '_blank', rel: 'noopener noreferrer')
+ - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project }
diff --git a/app/views/projects/clusters/gcp/login.html.haml b/app/views/projects/clusters/gcp/login.html.haml
index 790ba61fd86..e97ce01893a 100644
--- a/app/views/projects/clusters/gcp/login.html.haml
+++ b/app/views/projects/clusters/gcp/login.html.haml
@@ -5,7 +5,7 @@
.col-sm-4
= render 'projects/clusters/sidebar'
.col-sm-8
- = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Container Engine')
+ = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Kubernetes Engine')
= render 'header'
.row
.col-sm-8.col-sm-offset-4.signin-with-google
diff --git a/app/views/projects/clusters/gcp/new.html.haml b/app/views/projects/clusters/gcp/new.html.haml
index 9a79480c82f..8d92fb1e320 100644
--- a/app/views/projects/clusters/gcp/new.html.haml
+++ b/app/views/projects/clusters/gcp/new.html.haml
@@ -5,6 +5,6 @@
.col-sm-4
= render 'projects/clusters/sidebar'
.col-sm-8
- = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Container Engine')
+ = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Kubernetes Engine')
= render 'header'
= render 'form'
diff --git a/app/views/projects/clusters/new.html.haml b/app/views/projects/clusters/new.html.haml
index 2e5bc34f64a..ddd13f8ea96 100644
--- a/app/views/projects/clusters/new.html.haml
+++ b/app/views/projects/clusters/new.html.haml
@@ -7,7 +7,7 @@
.col-sm-8
%h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration')
- %p= s_('ClusterIntegration|Create a new cluster on Google Engine right from GitLab')
+ %p= s_('ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab')
= link_to s_('ClusterIntegration|Create on GKE'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
%p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster')
= link_to s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 5f607c2ab25..09934c09865 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -47,7 +47,7 @@
%li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch)
%li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff)
-.commit-box
+.commit-box{ data: { project_path: project_path(@project) } }
%h3.commit-title
= markdown(@commit.title, pipeline: :single_line, author: @commit.author)
- if @commit.description.present?
@@ -80,3 +80,13 @@
- if last_pipeline.duration
in
= time_interval_in_words last_pipeline.duration
+
+ - if @merge_request
+ .well-segment
+ = icon('info-circle fw')
+
+ This commit is part of merge request
+ = succeed '.' do
+ = link_to @merge_request.to_reference, diffs_project_merge_request_path(@project, @merge_request, commit_id: @commit.id)
+
+ Comments created here will be created in the context of that merge request.
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index abb292f8f27..2890e9d2b65 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -6,6 +6,9 @@
- @content_class = limited_container_width
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
+ = page_specific_javascript_bundle_tag('diff_notes')
.container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box"
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 1b91a94a9f8..618a6355d23 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,7 +1,18 @@
-- ref = local_assigns.fetch(:ref)
-
-- cache_key = [project.full_path, commit.id, current_application_settings, @path.presence, current_controller?(:commits), I18n.locale]
-- cache_key.push(commit.status(ref)) if commit.status(ref)
+- view_details = local_assigns.fetch(:view_details, false)
+- merge_request = local_assigns.fetch(:merge_request, nil)
+- project = local_assigns.fetch(:project) { merge_request&.project }
+- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
+
+- link = commit_path(project, commit, merge_request: merge_request)
+- cache_key = [project.full_path,
+ commit.id,
+ current_application_settings,
+ @path.presence,
+ current_controller?(:commits),
+ merge_request&.iid,
+ view_details,
+ commit.status(ref),
+ I18n.locale].compact
= cache(cache_key, expires_in: 1.day) do
%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
@@ -11,7 +22,7 @@
.commit-detail
.commit-content
- = link_to_markdown_field(commit, :title, project_commit_path(project, commit.id), class: "commit-row-message item-title")
+ = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
@@ -31,8 +42,7 @@
- commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
-
- .commit-actions.hidden-xs
+ .commit-actions.flex-row.hidden-xs
- if request.xhr?
= render partial: 'projects/commit/signature', object: commit.signature
- else
@@ -41,6 +51,9 @@
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
- = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent btn-link"
+ = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
+
+ - if view_details && merge_request
+ = link_to "View details", project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "btn btn-default"
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index d14897428d0..ac6852751be 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -1,4 +1,7 @@
-- ref = local_assigns.fetch(:ref)
+- merge_request = local_assigns.fetch(:merge_request, nil)
+- project = local_assigns.fetch(:project) { merge_request&.project }
+- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
+
- commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
@@ -8,7 +11,7 @@
%li.commits-row{ data: { day: day } }
%ul.content-list.commit-list.flex-list
- = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref }
+ = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref, merge_request: merge_request }
- if hidden > 0
%li.alert.alert-warning
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index ef305120525..ab371521840 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -3,7 +3,7 @@
- page_title _("Commits"), @ref
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+ = auto_discovery_link_tag(:atom, project_commits_path(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
.js-project-commits-show{ 'data-commits-limit' => @limit }
%div{ class: container_class }
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 1eccc0509bd..9779c1985d5 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -14,4 +14,4 @@
notes_path: notes_url,
last_fetched_at: Time.now.to_i,
noteable_data: serialize_issuable(@issue),
- current_user_data: UserSerializer.new.represent(current_user).to_json } }
+ current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } }
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 431d5a58daf..2f7aece7440 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -74,10 +74,10 @@
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
- #merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } }
+ #merge-requests{ data: { url: referenced_merge_requests_project_issue_path(@project, @issue) } }
// This element is filled in using JavaScript.
- #related-branches{ data: { url: related_branches_project_issue_url(@project, @issue) } }
+ #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }
// This element is filled in using JavaScript.
.content-block.emoji-block
diff --git a/app/views/projects/merge_requests/_commits.html.haml b/app/views/projects/merge_requests/_commits.html.haml
index 11793919ff7..b414518b597 100644
--- a/app/views/projects/merge_requests/_commits.html.haml
+++ b/app/views/projects/merge_requests/_commits.html.haml
@@ -5,4 +5,4 @@
= custom_icon ('illustration_no_commits')
- else
%ol#commits-list.list-unstyled
- = render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch
+ = render "projects/commits/commits", merge_request: @merge_request
diff --git a/app/views/projects/merge_requests/diffs/_commit_widget.html.haml b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
new file mode 100644
index 00000000000..2e5594f8cbe
--- /dev/null
+++ b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
@@ -0,0 +1,5 @@
+- if @commit
+ .info-well.hidden-xs.prepend-top-default
+ .well-segment
+ %ul.blob-commit-info
+ = render 'projects/commits/commit', commit: @commit, merge_request: @merge_request, view_details: true
diff --git a/app/views/projects/merge_requests/diffs/_different_base.html.haml b/app/views/projects/merge_requests/diffs/_different_base.html.haml
new file mode 100644
index 00000000000..0e57066f9c9
--- /dev/null
+++ b/app/views/projects/merge_requests/diffs/_different_base.html.haml
@@ -0,0 +1,11 @@
+- if @merge_request_diff && different_base?(@start_version, @merge_request_diff)
+ .mr-version-controls
+ .content-block
+ = icon('info-circle')
+ Selected versions have different base commits.
+ Changes will include
+ = link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
+ new commits
+ from
+ = succeed '.' do
+ %code.ref-name= @merge_request.target_branch
diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml
index 3d7a8f9d870..60c91024b23 100644
--- a/app/views/projects/merge_requests/diffs/_diffs.html.haml
+++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml
@@ -1,13 +1,18 @@
-- if @merge_request_diff.collected? || @merge_request_diff.overflow?
- = render 'projects/merge_requests/diffs/versions'
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
-- elsif @merge_request_diff.empty?
+= render 'projects/merge_requests/diffs/version_controls'
+= render 'projects/merge_requests/diffs/different_base'
+= render 'projects/merge_requests/diffs/not_all_comments_displayed'
+= render 'projects/merge_requests/diffs/commit_widget'
+
+- if @merge_request_diff&.empty?
.nothing-here-block
= image_tag 'illustrations/merge_request_changes_empty.svg'
- %p
- Nothing to merge from
- %strong= @merge_request.source_branch
- into
- %strong= @merge_request.target_branch
-
+ = succeed '.' do
+ No changes between
+ %span.ref-name= @merge_request.source_branch
+ and
+ %span.ref-name= @merge_request.target_branch
%p= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-save'
+- else
+ - diff_viewable = @merge_request_diff ? @merge_request_diff.collected? || @merge_request_diff.overflow? : true
+ - if diff_viewable
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
diff --git a/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
new file mode 100644
index 00000000000..529fbb8547a
--- /dev/null
+++ b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
@@ -0,0 +1,17 @@
+- if @commit || @start_version || (@merge_request_diff && !@merge_request_diff.latest?)
+ .mr-version-controls
+ .content-block.comments-disabled-notif.clearfix
+ = icon('info-circle')
+ = succeed '.' do
+ - if @commit
+ Only comments from the following commit are shown below
+ - else
+ Not all comments are displayed because you're
+ - if @start_version
+ comparing two versions of the diff
+ - else
+ viewing an old version of the diff
+ .pull-right
+ = link_to diffs_project_merge_request_path(@merge_request.project, @merge_request), class: 'btn btn-sm' do
+ Show latest version
+ = "of the diff" if @commit
diff --git a/app/views/projects/merge_requests/diffs/_versions.html.haml b/app/views/projects/merge_requests/diffs/_version_controls.html.haml
index 9f7152b9824..1c26f0405d2 100644
--- a/app/views/projects/merge_requests/diffs/_versions.html.haml
+++ b/app/views/projects/merge_requests/diffs/_version_controls.html.haml
@@ -1,4 +1,4 @@
-- if @merge_request_diffs.size > 1
+- if @merge_request_diff && @merge_request_diffs.size > 1
.mr-version-controls
.mr-version-menus-container.content-block
Changes between
@@ -71,27 +71,3 @@
(base)
%div
%strong.commit-sha= short_sha(@merge_request_diff.base_commit_sha)
-
- - if different_base?(@start_version, @merge_request_diff)
- .content-block
- = icon('info-circle')
- Selected versions have different base commits.
- Changes will include
- = link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
- new commits
- from
- = succeed '.' do
- %code= @merge_request.target_branch
-
- - if @start_version || !@merge_request_diff.latest?
- .comments-disabled-notif.content-block
- = icon('info-circle')
- Not all comments are displayed because you're
- - if @start_version
- comparing two versions
- - else
- viewing an old version
- of the diff.
-
- .pull-right
- = link_to 'Show latest version', diffs_project_merge_request_path(@project, @merge_request), class: 'btn btn-sm'
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index d88e3d794d3..abff702fd9d 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -8,7 +8,7 @@
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('diff_notes')
-.merge-request{ 'data-mr-action': "#{j params[:tab].presence || 'show'}", 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
+.merge-request{ data: { mr_action: j(params[:tab].presence || 'show'), url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project) } }
= render "projects/merge_requests/mr_title"
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
@@ -38,21 +38,21 @@
.nav-links.scrolling-tabs
%ul.merge-request-tabs
%li.notes-tab
- = link_to project_merge_request_path(@project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do
+ = tab_link_for @merge_request, :show, force_link: @commit.present? do
Discussion
%span.badge= @merge_request.related_notes.user.count
- if @merge_request.source_project
%li.commits-tab
- = link_to commits_project_merge_request_path(@project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+ = tab_link_for @merge_request, :commits do
Commits
%span.badge= @commits_count
- if @pipelines.any?
%li.pipelines-tab
- = link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
+ = tab_link_for @merge_request, :pipelines do
Pipelines
%span.badge.js-pipelines-mr-count= @pipelines.size
%li.diffs-tab
- = link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
+ = tab_link_for @merge_request, :diffs do
Changes
%span.badge= @merge_request.diff_size
#resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index cad7c2e83db..2f56630c22e 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -18,7 +18,8 @@
A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}.
%p
All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings.
- = brand_new_project_guidelines
+ .md
+ = brand_new_project_guidelines
.col-lg-9.js-toggle-container
%ul.nav-links.gitlab-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' }
@@ -85,7 +86,7 @@
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
- = link_to new_import_gitea_url, class: 'btn import_gitea' do
+ = link_to new_import_gitea_path, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index 4961835f12a..5ea653ccad5 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -3,7 +3,7 @@
%span.note-role.note-role-special.has-tooltip{ title: _("This is the author's first Merge Request to this project.") }
= issuable_first_contribution_icon
- if access.nonzero?
- %span.note-role.note-role-access= Gitlab::Access.human_access(access)
+ %span.note-role.user-access-role= Gitlab::Access.human_access(access)
- if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note)
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index ee4fa663b9f..c63e716180c 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -6,46 +6,35 @@
%h5 Auto DevOps (Beta)
%p
Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.
- This will happen starting with the next event (e.g.: push) that occurs to the project.
= link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md')
- message = auto_devops_warning_message(@project)
- if message
%p.settings-message.text-center
= message.html_safe
= f.fields_for :auto_devops_attributes, @auto_devops do |form|
- .radio.js-auto-devops-enable-radio-wrapper
+ .radio
= form.label :enabled_true do
- = form.radio_button :enabled, 'true', class: 'js-auto-devops-enable-radio'
+ = form.radio_button :enabled, 'true'
%strong Enable Auto DevOps
%br
%span.descr
The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project.
- - if show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(@project)
- .checkbox.hide.js-run-auto-devops-pipeline-checkbox-wrapper
- = label_tag 'project[run_auto_devops_pipeline_explicit]' do
- = check_box_tag 'project[run_auto_devops_pipeline_explicit]', true, false, class: 'js-run-auto-devops-pipeline-checkbox'
- = s_('ProjectSettings|Immediately run a pipeline on the default branch')
- .radio.js-auto-devops-enable-radio-wrapper
+ .radio
= form.label :enabled_false do
- = form.radio_button :enabled, 'false', class: 'js-auto-devops-enable-radio'
+ = form.radio_button :enabled, 'false'
%strong Disable Auto DevOps
%br
%span.descr
An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery.
- .radio.js-auto-devops-enable-radio-wrapper
+ .radio
= form.label :enabled_ do
- = form.radio_button :enabled, '', class: 'js-auto-devops-enable-radio'
+ = form.radio_button :enabled, ''
%strong Instance default (#{current_application_settings.auto_devops_enabled? ? 'enabled' : 'disabled'})
%br
%span.descr
Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>.
- - if show_run_auto_devops_pipeline_checkbox_for_instance_setting?(@project)
- .checkbox.hide.js-run-auto-devops-pipeline-checkbox-wrapper
- = label_tag 'project[run_auto_devops_pipeline_implicit]' do
- = check_box_tag 'project[run_auto_devops_pipeline_implicit]', true, false, class: 'js-run-auto-devops-pipeline-checkbox'
- = s_('ProjectSettings|Immediately run a pipeline on the default branch')
%p
You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml
index c06d1ffa59b..8ddb1b2bc99 100644
--- a/app/views/shared/_outdated_browser.html.haml
+++ b/app/views/shared/_outdated_browser.html.haml
@@ -1,7 +1,8 @@
- if outdated_browser?
- .browser-alert
- GitLab may not work properly because you are using an outdated web browser.
- %br
- Please install a
- = link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers')
- for a better experience.
+ .flash-container
+ .flash-alert.text-center
+ GitLab may not work properly because you are using an outdated web browser.
+ %br
+ Please install a
+ = link_to 'supported web browser', help_page_path('install/requirements', anchor: 'supported-web-browsers')
+ for a better experience.
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 321d8767d08..90395600d4e 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -1,19 +1,7 @@
-- group_member = local_assigns[:group_member]
-- full_name = true unless local_assigns[:full_name] == false
-- group_name = full_name ? group.full_name : group.name
-- css_class = '' unless local_assigns[:css_class]
-- css_class += " no-description" if group.description.blank?
-
-%li.group-row{ class: css_class }
- - if group_member
- .controls.hidden-xs
- - if can?(current_user, :admin_group, group)
- = link_to edit_group_path(group), class: "btn" do
- = sprite_icon('settings')
-
- = link_to leave_group_group_members_path(group), data: { confirm: leave_confirmation_message(group) }, method: :delete, class: "btn", title: s_("GroupsTree|Leave this group") do
- = icon('sign-out')
+- user = local_assigns.fetch(:user, current_user)
+- access = user&.max_member_access_for_group(group.id)
+%li.group-row{ class: ('no-description' if group.description.blank?) }
.stats
%span
= icon('bookmark')
@@ -30,11 +18,10 @@
= link_to group do
= group_icon(group, class: "avatar s40 hidden-xs")
.title
- = link_to group_name, group, class: 'group-name'
+ = link_to group.full_name, group, class: 'group-name'
- - if group_member
- as
- %span= group_member.human_access
+ - if access&.nonzero?
+ %span.user-access-role= Gitlab::Access.human_access(access)
- if group.description.present?
.description
diff --git a/app/views/shared/groups/_list.html.haml b/app/views/shared/groups/_list.html.haml
index aec8ecd1714..f50a6bd4d6a 100644
--- a/app/views/shared/groups/_list.html.haml
+++ b/app/views/shared/groups/_list.html.haml
@@ -1,6 +1,8 @@
- if groups.any?
+ - user = local_assigns[:user]
+
%ul.content-list
- groups.each_with_index do |group, i|
- = render "shared/groups/group", group: group
+ = render "shared/groups/group", group: group, user: user
- else
.nothing-here-block= s_("GroupsEmptyState|No groups found")
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index c6e18108c7a..e11f778adf5 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -27,7 +27,7 @@
- elsif discussion_locked
.disabled-comment.text-center.prepend-top-default
%span.issuable-note-warning
- %span.icon= sprite_icon('lock', size: 14)
+ = sprite_icon('lock', size: 16, css_class: 'icon')
%span
This
= issuable.class.to_s.titleize.downcase
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 0bedfea3502..e1da05d8f08 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -5,18 +5,20 @@
- forks = false unless local_assigns[:forks] == true
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
+- user = local_assigns[:user]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
- remote = false unless local_assigns[:remote] == true
-- load_pipeline_status(projects)
.js-projects-list-holder
- if any_projects?(projects)
+ - load_pipeline_status(projects)
+
%ul.projects-list
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
- forks: forks, show_last_commit_as_description: show_last_commit_as_description
+ forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user
- if @private_forks_count && @private_forks_count > 0
%li.project-row.private-forks-notice
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 98bfc7c4d36..003f5fa52eb 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -3,6 +3,8 @@
- forks = false unless local_assigns[:forks] == true
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
+- user = local_assigns[:user]
+- access = user&.max_member_access_for_project(project.id) unless user.nil?
- css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
@@ -21,14 +23,19 @@
.project-details
%h3.prepend-top-0.append-bottom-0
= link_to project_path(project), class: 'text-plain' do
- %span.project-full-name
+ %span.project-full-name><
%span.namespace-name
- if project.namespace && !skip_namespace
= project.namespace.human_name
\/
- %span.project-name
+ %span.project-name<
= project.name
+ - if access&.nonzero?
+ -# haml-lint:disable UnnecessaryStringOutput
+ = ' ' # prevent haml from eating the space between elements
+ %span.user-access-role= Gitlab::Access.human_access(access)
+
- if show_last_commit_as_description
.description.prepend-top-5
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
diff --git a/bin/storage_check b/bin/storage_check
new file mode 100755
index 00000000000..5a818732bd1
--- /dev/null
+++ b/bin/storage_check
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+
+require 'optparse'
+require 'net/http'
+require 'json'
+require 'socket'
+require 'logger'
+
+require_relative '../lib/gitlab/storage_check'
+
+Gitlab::StorageCheck::CLI.start!(ARGV)
diff --git a/changelogs/unreleased/15774-fix-39233-500-in-merge-request.yml b/changelogs/unreleased/15774-fix-39233-500-in-merge-request.yml
new file mode 100644
index 00000000000..1179b3f20e6
--- /dev/null
+++ b/changelogs/unreleased/15774-fix-39233-500-in-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: 'fix #39233 - 500 in merge request'
+merge_request: 15774
+author: Martin Nowak
+type: fixed
diff --git a/changelogs/unreleased/40555-replace-absolute-urls-with-related-branches-to-avoid-hostname.yml b/changelogs/unreleased/40555-replace-absolute-urls-with-related-branches-to-avoid-hostname.yml
new file mode 100644
index 00000000000..4f0eaf8472f
--- /dev/null
+++ b/changelogs/unreleased/40555-replace-absolute-urls-with-related-branches-to-avoid-hostname.yml
@@ -0,0 +1,6 @@
+---
+title: Fix related branches/Merge requests failing to load when the hostname setting
+ is changed
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/40573-rename-gke-as-kubernetes-engine.yml b/changelogs/unreleased/40573-rename-gke-as-kubernetes-engine.yml
new file mode 100644
index 00000000000..afbb869bdbb
--- /dev/null
+++ b/changelogs/unreleased/40573-rename-gke-as-kubernetes-engine.yml
@@ -0,0 +1,5 @@
+---
+title: Rename GKE as Kubernetes Engine
+merge_request: 15608
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/40715-updateendpoint-undefined-on-issue-page.yml b/changelogs/unreleased/40715-updateendpoint-undefined-on-issue-page.yml
new file mode 100644
index 00000000000..0328a693354
--- /dev/null
+++ b/changelogs/unreleased/40715-updateendpoint-undefined-on-issue-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix updateEndpoint undefined error for issue_show app root
+merge_request: 15698
+author:
+type: fixed
diff --git a/changelogs/unreleased/bvl-circuitbreaker-process.yml b/changelogs/unreleased/bvl-circuitbreaker-process.yml
new file mode 100644
index 00000000000..595dd13f724
--- /dev/null
+++ b/changelogs/unreleased/bvl-circuitbreaker-process.yml
@@ -0,0 +1,5 @@
+---
+title: Monitor NFS shards for circuitbreaker in a separate process
+merge_request: 15426
+author:
+type: changed
diff --git a/changelogs/unreleased/deploy-keys-loading-icon.yml b/changelogs/unreleased/deploy-keys-loading-icon.yml
new file mode 100644
index 00000000000..e3cb5bc6924
--- /dev/null
+++ b/changelogs/unreleased/deploy-keys-loading-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed deploy keys remove button loading state not resetting
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-commit-diff-discussions-in-mr-context.yml b/changelogs/unreleased/dm-commit-diff-discussions-in-mr-context.yml
new file mode 100644
index 00000000000..1f8b42ea21f
--- /dev/null
+++ b/changelogs/unreleased/dm-commit-diff-discussions-in-mr-context.yml
@@ -0,0 +1,5 @@
+---
+title: Make diff notes created on a commit in a merge request to persist a rebase.
+merge_request: 12148
+author:
+type: added
diff --git a/changelogs/unreleased/docs-add-why-do-i-get-signed-out-authentication-section.yml b/changelogs/unreleased/docs-add-why-do-i-get-signed-out-authentication-section.yml
new file mode 100644
index 00000000000..bc245880ed0
--- /dev/null
+++ b/changelogs/unreleased/docs-add-why-do-i-get-signed-out-authentication-section.yml
@@ -0,0 +1,5 @@
+---
+title: Add docs for why you might be signed out when using the Remember me token
+merge_request: 15756
+author:
+type: other
diff --git a/changelogs/unreleased/feature-sm-34834-missing-dependency-should-fail-job-2.yml b/changelogs/unreleased/feature-sm-34834-missing-dependency-should-fail-job-2.yml
new file mode 100644
index 00000000000..ab85b8ee515
--- /dev/null
+++ b/changelogs/unreleased/feature-sm-34834-missing-dependency-should-fail-job-2.yml
@@ -0,0 +1,5 @@
+---
+title: Fail jobs if its dependency is missing
+merge_request: 14009
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-new-project-guidelines-styling.yml b/changelogs/unreleased/fix-new-project-guidelines-styling.yml
new file mode 100644
index 00000000000..a97f5c485d4
--- /dev/null
+++ b/changelogs/unreleased/fix-new-project-guidelines-styling.yml
@@ -0,0 +1,5 @@
+---
+title: Use Markdown styling for new project guidelines
+merge_request: 15785
+author: Markus Koller
+type: fixed
diff --git a/changelogs/unreleased/fj-40752-forks-api-not-using-services.yml b/changelogs/unreleased/fj-40752-forks-api-not-using-services.yml
new file mode 100644
index 00000000000..cd7b87596e6
--- /dev/null
+++ b/changelogs/unreleased/fj-40752-forks-api-not-using-services.yml
@@ -0,0 +1,5 @@
+---
+title: Using appropiate services in the API for managing forks
+merge_request: 15709
+author:
+type: fixed
diff --git a/changelogs/unreleased/merge-request-lock-icon-size-fix.yml b/changelogs/unreleased/merge-request-lock-icon-size-fix.yml
new file mode 100644
index 00000000000..09c059a3011
--- /dev/null
+++ b/changelogs/unreleased/merge-request-lock-icon-size-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed merge request lock icon size
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/outdated-browser-position-fix.yml b/changelogs/unreleased/outdated-browser-position-fix.yml
new file mode 100644
index 00000000000..801e45a28b3
--- /dev/null
+++ b/changelogs/unreleased/outdated-browser-position-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed outdated browser flash positioning
+merge_request:
+author:
+type: fixed
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 43b1e943897..eb7959e4da6 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -11,14 +11,7 @@ Prometheus::Client.configure do |config|
config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir')
end
- config.pid_provider = -> do
- worker_id = Prometheus::Client::Support::Unicorn.worker_id
- if worker_id.nil?
- "process_pid_#{Process.pid}"
- else
- "worker_id_#{worker_id}"
- end
- end
+ config.pid_provider = Prometheus::Client::Support::Unicorn.method(:worker_pid_provider)
end
Gitlab::Application.configure do |config|
diff --git a/config/routes.rb b/config/routes.rb
index 4f27fea0e92..016140e0ede 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -42,6 +42,7 @@ Rails.application.routes.draw do
scope path: '-' do
get 'liveness' => 'health#liveness'
get 'readiness' => 'health#readiness'
+ post 'storage_check' => 'health#storage_check'
resources :metrics, only: [:index]
mount Peek::Railtie => '/peek'
diff --git a/config/routes/group.rb b/config/routes/group.rb
index db99e10bb9a..976837a246d 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -49,6 +49,12 @@ constraints(GroupUrlConstrainer.new) do
post :resend_invite, on: :member
delete :leave, on: :collection
end
+
+ resources :uploads, only: [:create] do
+ collection do
+ get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ }
+ end
+ end
end
scope(path: '*id',
diff --git a/db/migrate/20171123094802_add_circuitbreaker_check_interval_to_application_settings.rb b/db/migrate/20171123094802_add_circuitbreaker_check_interval_to_application_settings.rb
new file mode 100644
index 00000000000..213d46018fc
--- /dev/null
+++ b/db/migrate/20171123094802_add_circuitbreaker_check_interval_to_application_settings.rb
@@ -0,0 +1,20 @@
+class AddCircuitbreakerCheckIntervalToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :application_settings,
+ :circuitbreaker_check_interval,
+ :integer,
+ default: 1
+ end
+
+ def down
+ remove_column :application_settings,
+ :circuitbreaker_check_interval
+ end
+end
diff --git a/db/post_migrate/20171123101020_update_circuitbreaker_defaults.rb b/db/post_migrate/20171123101020_update_circuitbreaker_defaults.rb
new file mode 100644
index 00000000000..8e1c9e6d6bb
--- /dev/null
+++ b/db/post_migrate/20171123101020_update_circuitbreaker_defaults.rb
@@ -0,0 +1,34 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class UpdateCircuitbreakerDefaults < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ class ApplicationSetting < ActiveRecord::Base; end
+
+ def up
+ change_column_default :application_settings,
+ :circuitbreaker_failure_count_threshold,
+ 3
+ change_column_default :application_settings,
+ :circuitbreaker_storage_timeout,
+ 15
+
+ ApplicationSetting.update_all(circuitbreaker_failure_count_threshold: 3,
+ circuitbreaker_storage_timeout: 15)
+ end
+
+ def down
+ change_column_default :application_settings,
+ :circuitbreaker_failure_count_threshold,
+ 160
+ change_column_default :application_settings,
+ :circuitbreaker_storage_timeout,
+ 30
+
+ ApplicationSetting.update_all(circuitbreaker_failure_count_threshold: 160,
+ circuitbreaker_storage_timeout: 30)
+ end
+end
diff --git a/db/post_migrate/20171123101046_remove_old_circuitbreaker_config.rb b/db/post_migrate/20171123101046_remove_old_circuitbreaker_config.rb
new file mode 100644
index 00000000000..e646d4d3224
--- /dev/null
+++ b/db/post_migrate/20171123101046_remove_old_circuitbreaker_config.rb
@@ -0,0 +1,26 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveOldCircuitbreakerConfig < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ remove_column :application_settings,
+ :circuitbreaker_backoff_threshold
+ remove_column :application_settings,
+ :circuitbreaker_failure_wait_time
+ end
+
+ def down
+ add_column :application_settings,
+ :circuitbreaker_backoff_threshold,
+ :integer,
+ default: 80
+ add_column :application_settings,
+ :circuitbreaker_failure_wait_time,
+ :integer,
+ default: 30
+ end
+end
diff --git a/db/post_migrate/20171205190711_reschedule_fork_network_creation_caller.rb b/db/post_migrate/20171205190711_reschedule_fork_network_creation_caller.rb
new file mode 100644
index 00000000000..30ff5173192
--- /dev/null
+++ b/db/post_migrate/20171205190711_reschedule_fork_network_creation_caller.rb
@@ -0,0 +1,27 @@
+class RescheduleForkNetworkCreationCaller < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ MIGRATION = 'PopulateForkNetworksRange'.freeze
+ BATCH_SIZE = 100
+ DELAY_INTERVAL = 15.seconds
+
+ disable_ddl_transaction!
+
+ class ForkedProjectLink < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'forked_project_links'
+ end
+
+ def up
+ say 'Populating the `fork_networks` based on existing `forked_project_links`'
+
+ queue_background_migration_jobs_by_range_at_intervals(ForkedProjectLink, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+
+ def down
+ # nothing
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2be1b745342..4c697a4a384 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: 20171124150326) do
+ActiveRecord::Schema.define(version: 20171205190711) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -135,12 +135,10 @@ ActiveRecord::Schema.define(version: 20171124150326) do
t.boolean "hashed_storage_enabled", default: false, null: false
t.boolean "project_export_enabled", default: true, null: false
t.boolean "auto_devops_enabled", default: false, null: false
- t.integer "circuitbreaker_failure_count_threshold", default: 160
- t.integer "circuitbreaker_failure_wait_time", default: 30
+ t.integer "circuitbreaker_failure_count_threshold", default: 3
t.integer "circuitbreaker_failure_reset_time", default: 1800
- t.integer "circuitbreaker_storage_timeout", default: 30
+ t.integer "circuitbreaker_storage_timeout", default: 15
t.integer "circuitbreaker_access_retries", default: 3
- t.integer "circuitbreaker_backoff_threshold", default: 80
t.boolean "throttle_unauthenticated_enabled", default: false, null: false
t.integer "throttle_unauthenticated_requests_per_period", default: 3600, null: false
t.integer "throttle_unauthenticated_period_in_seconds", default: 3600, null: false
@@ -150,6 +148,7 @@ ActiveRecord::Schema.define(version: 20171124150326) do
t.boolean "throttle_authenticated_web_enabled", default: false, null: false
t.integer "throttle_authenticated_web_requests_per_period", default: 7200, null: false
t.integer "throttle_authenticated_web_period_in_seconds", default: 3600, null: false
+ t.integer "circuitbreaker_check_interval", default: 1, null: false
t.boolean "password_authentication_enabled_for_web"
t.boolean "password_authentication_enabled_for_git", default: true
t.integer "gitaly_timeout_default", default: 55, null: false
diff --git a/doc/README.md b/doc/README.md
index 95cb9683a15..11d52001440 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -13,13 +13,14 @@ GitLab offers the most scalable Git-based fully integrated platform for software
- **GitLab Community Edition (CE)** is an [opensource product](https://gitlab.com/gitlab-org/gitlab-ce/),
self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com.
- **GitLab Enterprise Edition (EE)** is an [opencore product](https://gitlab.com/gitlab-org/gitlab-ee/),
-self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)** and **GitLab Enterprise Edition Premium (EEP)**.
+self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)**, **GitLab Enterprise Edition Premium (EEP)**, and **GitLab Enterprise Edition Ultimate (EEU)**.
- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings).
> **GitLab EE** contains all features available in **GitLab CE**,
plus premium features available in each version: **Enterprise Edition Starter**
-(**EES**) and **Enterprise Edition Premium** (**EEP**). Everything available in
-**EES** is also available in **EEP**.
+(**EES**), **Enterprise Edition Premium** (**EEP**), and **Enterprise Edition Ultimate**
+(**EEU**). Everything available in **EES** is also available in **EEP**. Every feature
+available in **EEP** is also available in **EEU**.
----
@@ -32,8 +33,8 @@ Shortcuts to GitLab's most visited docs:
| [Using Docker images](ci/docker/using_docker_images.md) | [GitLab Pages](user/project/pages/index.md) |
- [User documentation](user/index.md)
-- [Administrator documentation](#administrator-documentation)
-- [Technical Articles](articles/index.md)
+- [Administrator documentation](administration/index.md)
+- [Contributor documentation](#contributor-documentation)
## Getting started with GitLab
@@ -133,83 +134,24 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
## Administrator documentation
-Learn how to administer your GitLab instance. Regular users don't
-have access to GitLab administration tools and settings.
+[Administration documentation](administration/index.md) applies to admin users of GitLab
+self-hosted instances:
-### Install, update, upgrade, migrate
+- GitLab Community Edition
+- GitLab [Enterprise Editions](https://about.gitlab.com/gitlab-ee/)
+ - Enterprise Edition Starter (EES)
+ - Enterprise Edition Premium (EEP)
+ - Enterprise Edition Ultimate (EEU)
-- [Install](install/README.md): Requirements, directory structures and installation from source.
-- [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/): Integrate [Mattermost](https://about.mattermost.com/) with your GitLab installation.
-- [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md): If you have an old GitLab installation (older than 8.0), follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
-- [Restart GitLab](administration/restart_gitlab.md): Learn how to restart GitLab and its components.
-- [Update](update/README.md): Update guides to upgrade your installation.
-
-### User permissions
-
-- [Access restrictions](user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab
-- [Authentication/Authorization](topics/authentication/index.md#gitlab-administrators): Enforce 2FA, configure external authentication with LDAP, SAML, CAS and additional Omniauth providers.
-
-### Features
-
-- [Container Registry](administration/container_registry.md): Configure Docker Registry with GitLab.
-- [Custom Git hooks](administration/custom_hooks.md): Custom Git hooks (on the filesystem) for when webhooks aren't enough.
-- [Git LFS configuration](workflow/lfs/lfs_administration.md): Learn how to use LFS under GitLab.
-- [GitLab Pages configuration](administration/pages/index.md): Configure GitLab Pages.
-- [High Availability](administration/high_availability/README.md): Configure multiple servers for scaling or high availability.
-- [User cohorts](user/admin_area/user_cohorts.md): View user activity over time.
-- [Web terminals](administration/integration/terminal.md): Provide terminal access to environments from within GitLab.
-- GitLab CI
- - [CI admin settings](user/admin_area/settings/continuous_integration.md): Define max artifacts size and expiration time.
-
-### Integrations
-
-- [Integrations](integration/README.md): How to integrate with systems such as JIRA, Redmine, Twitter.
-- [Mattermost](user/project/integrations/mattermost.md): Set up GitLab with Mattermost.
-
-### Monitoring
-
-- [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md): Configure GitLab and InfluxDB for measuring performance metrics.
-- [GitLab performance monitoring with Prometheus](administration/monitoring/prometheus/index.md): Configure GitLab and Prometheus for measuring performance metrics.
-- [Monitoring uptime](user/admin_area/monitoring/health_check.md): Check the server status using the health check endpoint.
-- [Monitoring GitHub imports](administration/monitoring/github_imports.md)
-
-### Performance
-
-- [Housekeeping](administration/housekeeping.md): Keep your Git repository tidy and fast.
-- [Operations](administration/operations.md): Keeping GitLab up and running.
-- [Polling](administration/polling.md): Configure how often the GitLab UI polls for updates.
-- [Request Profiling](administration/monitoring/performance/request_profiling.md): Get a detailed profile on slow requests.
-- [Performance Bar](administration/monitoring/performance/performance_bar.md): Get performance information for the current page.
-
-### Customization
-
-- [Adjust your instance's timezone](workflow/timezone.md): Customize the default time zone of GitLab.
-- [Environment variables](administration/environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
-- [Header logo](customization/branded_page_and_email_header.md): Change the logo on the overall page and email header.
-- [Issue closing pattern](administration/issue_closing_pattern.md): Customize how to close an issue from commit messages.
-- [Libravatar](customization/libravatar.md): Use Libravatar instead of Gravatar for user avatars.
-- [Welcome message](customization/welcome_message.md): Add a custom welcome message to the sign-in page.
-- [New project page](customization/new_project_page.md): Customize the new project page.
-
-### Admin tools
-
-- [Gitaly](administration/gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service
-- [Raketasks](raketasks/README.md): Backups, maintenance, automatic webhook setup and the importing of projects.
- - [Backup and restore](raketasks/backup_restore.md): Backup and restore your GitLab instance.
-- [Reply by email](administration/reply_by_email.md): Allow users to comment on issues and merge requests by replying to notification emails.
-- [Repository checks](administration/repository_checks.md): Periodic Git repository checks.
-- [Repository storage paths](administration/repository_storage_paths.md): Manage the paths used to store repositories.
-- [Security](security/README.md): Learn what you can do to further secure your GitLab instance.
-- [System hooks](system_hooks/system_hooks.md): Notifications when users, projects and keys are changed.
-
-### Troubleshooting
-
-- [Debugging tips](administration/troubleshooting/debug.md): Tips to debug problems when things go wrong
-- [Log system](administration/logs.md): Where to look for logs.
-- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md): Debug when Sidekiq appears hung and is not processing jobs.
+Learn how to install, configure, update, upgrade, integrate, and maintain your own instance.
+Regular users don't have access to GitLab administration tools and settings.
## Contributor documentation
+GitLab Community Edition is [opensource](https://gitlab.com/gitlab-org/gitlab-ce/)
+and Enterprise Editions are [opencore](https://gitlab.com/gitlab-org/gitlab-ee/).
+Learn how to contribute to GitLab:
+
- [Development](development/README.md): All styleguides and explanations how to contribute.
- [Legal](legal/README.md): Contributor license agreements.
- [Writing documentation](development/writing_documentation.md): Contributing to GitLab Docs.
diff --git a/doc/administration/index.md b/doc/administration/index.md
new file mode 100644
index 00000000000..c8d28d8485a
--- /dev/null
+++ b/doc/administration/index.md
@@ -0,0 +1,121 @@
+# Administrator documentation
+
+Learn how to administer your GitLab instance (Community Edition and
+[Enterprise Editions](https://about.gitlab.com/gitlab-ee/)).
+Regular users don't have access to GitLab administration tools and settings.
+
+GitLab.com is administered by GitLab, Inc., therefore, only GitLab team members have
+access to its admin configurations. If you're a GitLab.com user, please check the
+[user documentation](../user/index.html).
+
+## Installing and maintaining GitLab
+
+Learn how to install, configure, update, and maintain your GitLab instance.
+
+### Installing GitLab
+
+- [Install](../install/README.md): Requirements, directory structures, and installation methods.
+- [High Availability](high_availability/README.md): Configure multiple servers for scaling or high availability.
+
+### Configuring GitLab
+
+- [Adjust your instance's timezone](../workflow/timezone.md): Customize the default time zone of GitLab.
+- [Header logo](../customization/branded_page_and_email_header.md): Change the logo on all pages and email headers.
+- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
+- [System hooks](../system_hooks/system_hooks.md): Notifications when users, projects and keys are changed.
+- [Security](../security/README.md): Learn what you can do to further secure your GitLab instance.
+- [Usage statistics, version check, and usage ping](../user/admin_area/settings/usage_statistics.md): Enable or disable information about your instance to be sent to GitLab, Inc.
+- [Polling](polling.md): Configure how often the GitLab UI polls for updates.
+- [GitLab Pages configuration](pages/index.md): Enable and configure GitLab Pages.
+- [GitLab Pages configuration for installations from the source](pages/source.md): Enable and configure GitLab Pages on
+[source installations](../install/installation.md#installation-from-source).
+- [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
+
+### Maintaining GitLab
+
+- [Raketasks](../raketasks/README.md): Perform various tasks for maintenance, backups, automatic webhooks setup, etc.
+ - [Backup and restore](../raketasks/backup_restore.md): Backup and restore your GitLab instance.
+- [Operations](operations.md): Keeping GitLab up and running (clean up Redis sessions, moving repositories, Sidekiq Job throttling, Sidekiq MemoryKiller, Unicorn).
+- [Restart GitLab](restart_gitlab.md): Learn how to restart GitLab and its components.
+
+#### Updating GitLab
+
+- [GitLab versions and maintenance policy](../policy/maintenance.md): Understand GitLab versions and releases (Major, Minor, Patch, Security), as well as update recommendations.
+- [Update GitLab](../update/README.md): Update guides to upgrade your installation to a new version.
+- [Downtimeless updates](../update/README.md#upgrading-without-downtime): Upgrade to a newer major, minor, or patch version of GitLab without taking your GitLab instance offline.
+- [Migrate your GitLab CI/CD data to another version of GitLab](../migrate_ci_to_ce/README.md): If you have an old GitLab installation (older than 8.0), follow this guide to migrate your existing GitLab CI/CD data to another version of GitLab.
+
+### Upgrading or downgrading GitLab
+
+- [Upgrade from GitLab CE to GitLab EE](../update/README.md#upgrading-between-editions): learn how to upgrade GitLab Community Edition to GitLab Enterprise Editions.
+- [Downgrade from GitLab EE to GitLab CE](../downgrade_ee_to_ce/README.md): Learn how to downgrade GitLab Enterprise Editions to Community Edition.
+
+### GitLab platform integrations
+
+- [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/): Integrate with [Mattermost](https://about.mattermost.com/), an open source, private cloud workplace for web messaging.
+- [PlantUML](integration/plantuml.md): Create simple diagrams in AsciiDoc and Markdown documents
+created in snippets, wikis, and repos.
+- [Web terminals](integration/terminal.md): Provide terminal access to your applications deployed to Kubernetes from within GitLab's CI/CD [environments](../ci/environments.md#web-terminals).
+
+## User settings and permissions
+
+- [Libravatar](../customization/libravatar.md): Use Libravatar instead of Gravatar for user avatars.
+- [Sign-up restrictions](../user/admin_area/settings/sign_up_restrictions.md): block email addresses of specific domains, or whitelist only specific domains.
+- [Access restrictions](../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab (SSH, HTTP, HTTPS).
+- [Authentication/Authorization](../topics/authentication/index.md#gitlab-administrators): Enforce 2FA, configure external authentication with LDAP, SAML, CAS and additional Omniauth providers.
+- [Reply by email](reply_by_email.md): Allow users to comment on issues and merge requests by replying to notification emails.
+ - [Postfix for Reply by email](reply_by_email_postfix_setup.md): Set up a basic Postfix mail
+server with IMAP authentication on Ubuntu, to be used with Reply by email.
+- [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time.
+
+## Project settings
+
+- [Container Registry](container_registry.md): Configure Container Registry with GitLab.
+- [Issue closing pattern](issue_closing_pattern.md): Customize how to close an issue from commit messages.
+- [Gitaly](gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service.
+- [Default labels](../user/admin_area/labels.html): Create labels that will be automatically added to every new project.
+
+### Repository settings
+
+- [Repository checks](repository_checks.md): Periodic Git repository checks.
+- [Repository storage paths](repository_storage_paths.md): Manage the paths used to store repositories.
+- [Repository storage rake tasks](raketasks/storage.md): A collection of rake tasks to list and migrate existing projects and attachments associated with it from Legacy storage to Hashed storage.
+
+## Continuous Integration settings
+
+- [Enable/disable GitLab CI/CD](../ci/enable_or_disable_ci.md#site-wide-admin-setting): Enable or disable GitLab CI/CD for your instance.
+- [GitLab CI/CD admin settings](../user/admin_area/settings/continuous_integration.md): Define max artifacts size and expiration time.
+- [Job artifacts](job_artifacts.md): Enable, disable, and configure job artifacts (a set of files and directories which are outputted by a job when it completes successfully).
+- [Artifacts size and expiration](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size): Define maximum artifacts limits and expiration date.
+- [Register Shared and specific Runners](../ci/runners/README.md#registering-a-shared-runner): Learn how to register and configure Shared and specific Runners to your own instance.
+- [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota): Limit the usage of pipeline minutes for Shared Runners.
+- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enabling-auto-devops): Enable or disable Auto DevOps for your instance.
+
+## Git configuration options
+
+- [Custom Git hooks](custom_hooks.md): Custom Git hooks (on the filesystem) for when webhooks aren't enough.
+- [Git LFS configuration](../workflow/lfs/lfs_administration.md): Learn how to configure LFS for GitLab.
+- [Housekeeping](housekeeping.md): Keep your Git repositories tidy and fast.
+
+## Monitoring GitLab
+
+- [Monitoring uptime](../user/admin_area/monitoring/health_check.md): Check the server status using the health check endpoint.
+ - [IP whitelist](monitoring/ip_whitelist.md): Monitor endpoints that provide health check information when probed.
+- [Monitoring GitHub imports](monitoring/github_imports.md): GitLab's GitHub Importer displays Prometheus metrics to monitor the health and progress of the importer.
+- [Conversational Development (ConvDev) Index](../user/admin_area/monitoring/convdev.md): Provides an overview of your entire instance's feature usage.
+
+### Performance Monitoring
+
+- [GitLab Performance Monitoring](monitoring/performance/gitlab_configuration.md): Enable GitLab Performance Monitoring.
+- [GitLab performance monitoring with InfluxDB](monitoring/performance/introduction.md): Configure GitLab and InfluxDB for measuring performance metrics.
+ - [InfluxDB Schema](monitoring/performance/influxdb_schema.md): Measurements stored in InfluxDB.
+- [GitLab performance monitoring with Prometheus](monitoring/prometheus/index.md): Configure GitLab and Prometheus for measuring performance metrics.
+- [GitLab performance monitoring with Grafana](monitoring/prometheus/index.md): Configure GitLab to visualize time series metrics through graphs and dashboards.
+- [Request Profiling](monitoring/performance/request_profiling.md): Get a detailed profile on slow requests.
+- [Performance Bar](monitoring/performance/performance_bar.md): Get performance information for the current page.
+
+## Troubleshooting
+
+- [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong
+- [Log system](logs.md): Where to look for logs.
+- [Sidekiq Troubleshooting](troubleshooting/sidekiq.md): Debug when Sidekiq appears hung and is not processing jobs.
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 86b436d89dd..33f8a69c249 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -128,6 +128,45 @@ steps below.
1. Save the file and [restart GitLab][] for the changes to take effect.
+## Validation for dependencies
+
+> Introduced in GitLab 10.3.
+
+To disable [the dependencies validation](../ci/yaml/README.md#when-a-dependent-job-will-fail),
+you can flip the feature flag from a Rails console.
+
+---
+
+**In Omnibus installations:**
+
+1. Enter the Rails console:
+
+ ```sh
+ sudo gitlab-rails console
+ ```
+
+1. Flip the switch and disable it:
+
+ ```ruby
+ Feature.enable('ci_disable_validates_dependencies')
+ ```
+---
+
+**In installations from source:**
+
+1. Enter the Rails console:
+
+ ```sh
+ cd /home/git/gitlab
+ RAILS_ENV=production sudo -u git -H bundle exec rails console
+ ```
+
+1. Flip the switch and disable it:
+
+ ```ruby
+ Feature.enable('ci_disable_validates_dependencies')
+ ```
+
## Set the maximum file size of the artifacts
Provided the artifacts are enabled, you can change the maximum file size of the
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 0c63b0b59a7..7d47aaac299 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -58,6 +58,9 @@ Before proceeding with the Pages configuration, you will need to:
so that your users don't have to bring their own.
1. (Only for custom domains) Have a **secondary IP**.
+NOTE: **Note:**
+If your GitLab instance and the Pages daemon are deployed in a private network or behind a firewall, your GitLab Pages websites will only be accessible to devices/users that have access to the private network.
+
### DNS configuration
GitLab Pages expect to run on their own virtual host. In your DNS server/provider
diff --git a/doc/api/protected_branches.md b/doc/api/protected_branches.md
index 81fe854060a..950ead52560 100644
--- a/doc/api/protected_branches.md
+++ b/doc/api/protected_branches.md
@@ -136,7 +136,7 @@ DELETE /projects/:id/protected_branches/:name
```
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/*-stable'
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/*-stable'
```
| Attribute | Type | Required | Description |
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 22fb2baa8ec..0e4758cda2d 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -70,10 +70,9 @@ PUT /application/settings
| `akismet_api_key` | string | no | API key for akismet spam protection |
| `akismet_enabled` | boolean | no | Enable or disable akismet spam protection |
| `circuitbreaker_access_retries | integer | no | The number of attempts GitLab will make to access a storage. |
-| `circuitbreaker_backoff_threshold | integer | no | The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host. |
+| `circuitbreaker_check_interval` | integer | no | Number of seconds in between storage checks. |
| `circuitbreaker_failure_count_threshold` | integer | no | The number of failures of after which GitLab will completely prevent access to the storage. |
| `circuitbreaker_failure_reset_time` | integer | no | Time in seconds GitLab will keep storage failure information. When no failures occur during this time, the failure information is reset. |
-| `circuitbreaker_failure_wait_time` | integer | no | Time in seconds GitLab will block access to a failing storage to allow it to recover. |
| `circuitbreaker_storage_timeout` | integer | no | Seconds to wait for a storage access attempt |
| `clientside_sentry_dsn` | string | no | Required if `clientside_sentry_dsn` is enabled |
| `clientside_sentry_enabled` | boolean | no | Enable Sentry error reporting for the client side |
diff --git a/doc/ci/autodeploy/quick_start_guide.md b/doc/ci/autodeploy/quick_start_guide.md
index f76c2a2cf31..cc6c9ec0e0a 100644
--- a/doc/ci/autodeploy/quick_start_guide.md
+++ b/doc/ci/autodeploy/quick_start_guide.md
@@ -11,11 +11,11 @@ We made a minimal [Ruby application](https://gitlab.com/gitlab-examples/minimal-
Let’s start by forking our sample application. Go to [the project page](https://gitlab.com/gitlab-examples/minimal-ruby-app) and press the `Fork` button. Soon you should have a project under your namespace with the necessary files.
-## Setup your own cluster on Google Container Engine
+## Setup your own cluster on Google Kubernetes Engine
If you do not already have a Google Cloud account, create one at https://console.cloud.google.com.
-Visit the [`Container Engine`](https://console.cloud.google.com/kubernetes/list) tab and create a new cluster. You can change the name and leave the rest of the default settings. Once you have your cluster running, you need to connect to the cluster by following the Google interface.
+Visit the [`Kubernetes Engine`](https://console.cloud.google.com/kubernetes/list) tab and create a new cluster. You can change the name and leave the rest of the default settings. Once you have your cluster running, you need to connect to the cluster by following the Google interface.
## Connect to Kubernetes cluster
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index f40d2c5e347..32464cbb259 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1153,6 +1153,20 @@ deploy:
script: make deploy
```
+#### When a dependent job will fail
+
+> Introduced in GitLab 10.3.
+
+If the artifacts of the job that is set as a dependency have been
+[expired](#artifacts-expire_in) or
+[erased](../../user/project/pipelines/job_artifacts.md#erasing-artifacts), then
+the dependent job will fail.
+
+NOTE: **Note:**
+You can ask your administrator to
+[flip this switch](../../administration/job_artifacts.md#validation-for-dependencies)
+and bring back the old behavior.
+
### before_script and after_script
It's possible to overwrite the globally defined `before_script` and `after_script`:
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index fa564d83785..96968c1e3ab 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -1,7 +1,7 @@
# GitLab Helm Chart
> **Note**:
* This chart is deprecated, and is being replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). For more information on available charts, please see our [overview](index.md#chart-overview).
-* These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
+* These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview).
@@ -243,7 +243,7 @@ controller. For `nginx-ingress` you can check the
on how to add the annotation to the `controller.service.annotations` array.
>**Note:**
-When using the `nginx-ingress` controller on Google Container Engine (GKE), and using the `external-traffic` annotation,
+When using the `nginx-ingress` controller on Google Kubernetes Engine (GKE), and using the `external-traffic` annotation,
you will need to additionally set the `controller.kind` to be DaemonSet. Otherwise only pods running on the same node
as the nginx controller will be able to reach GitLab. This may result in pods within your cluster not being able to reach GitLab.
See the [Kubernetes documentation](https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer) and
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index 6659c3cf7b2..5a5f8d67ff5 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -1,7 +1,7 @@
# GitLab-Omnibus Helm Chart
> **Note:**
* This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md).
-* These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
+* These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
@@ -72,7 +72,7 @@ Other common configuration options:
- `baseIP`: the desired [external IP address](#external-ip-recommended)
- `gitlab`: Choose the [desired edition](https://about.gitlab.com/products), either `ee` or `ce`. `ce` is the default.
- `gitlabEELicense`: For Enterprise Edition, the [license](https://docs.gitlab.com/ee/user/admin_area/license.html) can be installed directly via the Chart
-- `provider`: Optimizes the deployment for a cloud provider. The default is `gke` for [Google Container Engine](https://cloud.google.com/container-engine/), with `acs` also supported for the [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/).
+- `provider`: Optimizes the deployment for a cloud provider. The default is `gke` for [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/), with `acs` also supported for the [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/).
For additional configuration options, consult the [values.yaml](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-omnibus/values.yaml).
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index 5e0d7493b61..ca9c95aeced 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -1,6 +1,6 @@
# GitLab Runner Helm Chart
> **Note:**
-These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
+These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
Kubernetes cluster.
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index dd350820c18..0932e1eee3a 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -1,5 +1,5 @@
# Installing GitLab on Kubernetes
-> **Note**: These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
+> **Note**: These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
The easiest method to deploy GitLab on [Kubernetes](https://kubernetes.io/) is
to take advantage of GitLab's Helm charts. [Helm] is a package
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 597c98fbf6b..1f30909b0aa 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -6,6 +6,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [SSH](../../ssh/README.md)
- [Two-Factor Authentication (2FA)](../../user/profile/account/two_factor_authentication.md#two-factor-authentication)
+- [Why do I keep getting signed out?](../../user/profile/index.md#why-do-i-keep-getting-signed-out)
- **Articles:**
- [Support for Universal 2nd Factor Authentication - YubiKeys](https://about.gitlab.com/2016/06/22/gitlab-adds-support-for-u2f/)
- [Security Webcast with Yubico](https://about.gitlab.com/2016/08/31/gitlab-and-yubico-security-webcast/)
diff --git a/doc/topics/autodevops/img/auto_devops_settings.png b/doc/topics/autodevops/img/auto_devops_settings.png
index b572cc5b855..067c9da3fdc 100644
--- a/doc/topics/autodevops/img/auto_devops_settings.png
+++ b/doc/topics/autodevops/img/auto_devops_settings.png
Binary files differ
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 914217772b8..d100b431721 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -129,8 +129,6 @@ full use of Auto DevOps. If this is your fist time, we recommend you follow the
1. Go to your project's **Settings > CI/CD > General pipelines settings** and
find the Auto DevOps section
1. Select "Enable Auto DevOps"
-1. After selecting an option to enable Auto DevOps, a checkbox will appear below
- so you can immediately run a pipeline on the default branch
1. Optionally, but recommended, add in the [base domain](#auto-devops-base-domain)
that will be used by Kubernetes to deploy your application
1. Hit **Save changes** for the changes to take effect
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index ffe05519d7b..4858735ee86 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -23,12 +23,12 @@ page](https://gitlab.com/auto-devops-examples/minimal-ruby-app) and press the
**Fork** button. Soon you should have a project under your namespace with the
necessary files.
-## Setup your own cluster on Google Container Engine
+## Setup your own cluster on Google Kubernetes Engine
If you do not already have a Google Cloud account, create one at
https://console.cloud.google.com.
-Visit the [**Container Engine**](https://console.cloud.google.com/kubernetes/list)
+Visit the [**Kubernetes Engine**](https://console.cloud.google.com/kubernetes/list)
tab and create a new cluster. You can change the name and leave the rest of the
default settings. Once you have your cluster running, you need to connect to the
cluster by following the Google interface.
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 5fcc0501dc1..04e615330ce 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -1,8 +1,32 @@
# User account
-When logged into their GitLab account, users can customize their
+When signed into their GitLab account, users can customize their
experience according to the best approach to their cases.
+## Signing in
+
+There are several ways to sign into your GitLab account.
+See the [authentication topic](../../topics/authentication/index.md) for more details.
+
+### Why do I keep getting signed out?
+
+When signing in to the main GitLab application, a `_gitlab_session` cookie is
+set. `_gitlab_session` is cleared client-side when you close your browser
+and expires after "Application settings -> Session duration (minutes)"/`session_expire_delay`
+(defaults to `10080` minutes = 7 days).
+
+When signing in to the main GitLab application, you can also check the
+"Remember me" option which sets the `remember_user_token`
+cookie (via [`devise`](https://github.com/plataformatec/devise)).
+`remember_user_token` expires after
+`config/initializers/devise.rb` -> `config.remember_for` (defaults to 2 weeks).
+
+When the `_gitlab_session` expires or isn't available, GitLab uses the `remember_user_token`
+to get you a new `_gitlab_session` and keep you signed in through browser restarts.
+
+After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
+you will be asked to sign in again to verify your identity (which is for security reasons).
+
## Username
Your `username` is a unique [`namespace`](../group/index.md#namespaces)
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 97d0d529886..5d91aef5735 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -64,7 +64,7 @@ common actions on issues or merge requests
- [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job),
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- [GKE cluster integration](clusters/index.md): Connecting your GitLab project
- with Google Container Engine
+ with Google Kubernetes Engine
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index abe6b4cbd8e..8404d789de6 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -1,49 +1,78 @@
-# GitLab Pages documentation
-
-With GitLab Pages you can create static websites for your GitLab projects,
-groups, or user accounts. You can use any static website generator: Jekyll,
-Middleman, Hexo, Hugo, Pelican, you name it! Connect as many customs domains
-as you like and bring your own TLS certificate to secure them.
-
-Here's some info we've gathered to get you started.
-
-## General info
-
-- [Product webpage](https://pages.gitlab.io)
-- ["We're bringing GitLab Pages to CE"](https://about.gitlab.com/2016/12/24/were-bringing-gitlab-pages-to-community-edition/)
-- [Pages group - templates](https://gitlab.com/pages)
-- [General user documentation](introduction.md)
-- [Admin documentation - Set GitLab Pages on your own GitLab instance](../../../administration/pages/index.md)
-- ["We are changing the IP of GitLab Pages on GitLab.com"](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/)
-
-## Getting started
-
-- **GitLab Pages from A to Z**
- - [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md)
- - [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md)
- - [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md)
- - [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md)
-- **Static Site Generators - Blog posts series**
- - [SSGs part 1: Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
- - [SSGs part 2: Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/)
- - [SSGs part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
-- **Secure GitLab Pages custom domain with SSL/TLS certificates**
- - [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/)
- - [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
- - [StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
-- **General**
- - [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) a comprehensive step-by-step guide
- - [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/)
-
-## Video tutorials
-
-- [How to publish a website with GitLab Pages on GitLab.com: from a forked project](https://youtu.be/TWqh9MtT4Bg)
-- [How to Enable GitLab Pages for GitLab CE and EE (for Admins only)](https://youtu.be/dD8c7WNcc6s)
+# GitLab Pages
+
+With GitLab Pages you can host your website at no cost.
+
+Your files live in a GitLab project's [repository](../repository/index.md),
+from which you can deploy [static websites](#explore-gitlab-pages).
+GitLab Pages supports all static site generators (SSGs).
+
+## Getting Started
+
+Follow the steps below to get your website live. They shouldn't take more than
+5 minutes to complete:
+
+- 1. [Fork](../../../gitlab-basics/fork-project.md#how-to-fork-a-project) an [example project](https://gitlab.com/pages)
+- 2. Change a file to trigger a GitLab CI/CD pipeline
+- 3. Visit your project's **Settings > Pages** to see your **website link**, and click on it. Bam! Your website is live.
+
+_Further steps (optional):_
+
+- 4. Remove the [fork relationship](getting_started_part_two.md#fork-a-project-to-get-started-from) (_You don't need the relationship unless you intent to contribute back to the example project you forked from_).
+- 5. Make it a [user/group website](getting_started_part_one.md#user-and-group-websites)
+
+**Watch a video with the steps above: https://www.youtube.com/watch?v=TWqh9MtT4Bg**
+
+_Advanced options:_
+
+- [Use a custom domain](getting_started_part_three.md#adding-your-custom-domain-to-gitlab-pages)
+- Apply [SSL/TLS certification](getting_started_part_three.md#ssl-tls-certificates) to your custom domain
+
+## Explore GitLab Pages
+
+With GitLab Pages you can create [static websites](getting_started_part_one.md#what-you-need-to-know-before-getting-started)
+for your GitLab projects, groups, or user accounts. You can use any static
+website generator: Jekyll, Middleman, Hexo, Hugo, Pelican, you name it!
+Connect as many custom domains as you like and bring your own TLS certificate
+to secure them.
+
+Read the following tutorials to know more about:
+
+- [Static websites and GitLab Pages domains](getting_started_part_one.md)
+- [Forking projects and creating new ones from scratch, URLs and baseurls](getting_started_part_two.md)
+- [Custom domains and subdomains, DNS records, SSL/TLS certificates](getting_started_part_three.md)
+- [How to create your own `.gitlab-ci.yml` for your site](getting_started_part_four.md)
+- [Technical aspects, custom 404 pages, limitations](introduction.md)
+- [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) (outdated)
+
+_Blog posts series about Static Site Generators (SSGs):_
+
+- [SSGs part 1: Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
+- [SSGs part 2: Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/)
+- [SSGs part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
+
+_Blog posts for securing GitLab Pages custom domains with SSL/TLS certificates:_
+
+- [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
+- [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) (outdated)
+- [StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/) (deprecated)
## Advanced use
-- **Blog Posts**
- - [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
- - [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
- - [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
- - [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
+- [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/)
+- [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
+- [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
+- [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+- [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
+
+## Admin GitLab Pages for CE and EE
+
+Enable and configure GitLab Pages on your own instance (GitLab Community Edition and Enterprise Editions) with
+the [admin guide](../../../administration/pages/index.md).
+
+**Watch the video: https://www.youtube.com/watch?v=dD8c7WNcc6s**
+
+## More information about GitLab Pages
+
+- For an overview, visit the [feature webpage](https://about.gitlab.com/features/pages/)
+- Announcement (2016-12-24): ["We're bringing GitLab Pages to CE"](https://about.gitlab.com/2016/12/24/were-bringing-gitlab-pages-to-community-edition/)
+- Announcement (2017-03-06): ["We are changing the IP of GitLab Pages on GitLab.com"](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/)
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index f9a268fb789..402989f4508 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -44,7 +44,7 @@ the artifacts will be kept forever.
For more examples on artifacts, follow the [artifacts reference in
`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifacts).
-## Browsing job artifacts
+## Browsing artifacts
>**Note:**
With GitLab 9.2, PDFs, images, videos and other formats can be previewed
@@ -77,7 +77,7 @@ one HTML file that you can view directly online when
---
-## Downloading job artifacts
+## Downloading artifacts
If you need to download the whole archive, there are buttons in various places
inside GitLab that make that possible.
@@ -102,7 +102,7 @@ inside GitLab that make that possible.
![Job artifacts browser](img/job_artifacts_browser.png)
-## Downloading the latest job artifacts
+## Downloading the latest artifacts
It is possible to download the latest artifacts of a job via a well known URL
so you can use it for scripting purposes.
@@ -163,6 +163,18 @@ information in the UI.
![Latest artifacts button](img/job_latest_artifacts_browser.png)
+## Erasing artifacts
+
+DANGER: **Warning:**
+This is a destructive action that leads to data loss. Use with caution.
+
+If you have at least Developer [permissions](../../permissions.md#gitlab-ci-cd-permissions)
+on the project, you can erase a single job via the UI which will also remove the
+artifacts and the job's trace.
+
+1. Navigate to a job's page.
+1. Click the trash icon at the top right of the job's trace.
+1. Confirm the deletion.
[expiry date]: ../../../ci/yaml/README.md#artifacts-expire_in
[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399
diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb
index 118883f5ea5..598c76f6168 100644
--- a/lib/api/circuit_breakers.rb
+++ b/lib/api/circuit_breakers.rb
@@ -41,7 +41,7 @@ module API
detail 'This feature was introduced in GitLab 9.5'
end
delete do
- Gitlab::Git::Storage::CircuitBreaker.reset_all!
+ Gitlab::Git::Storage::FailureInfo.reset_all!
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 62ee20bf7de..d96e7f2770f 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -16,10 +16,13 @@ module API
class UserBasic < UserSafe
expose :state
+
expose :avatar_url do |user, options|
user.avatar_url(only_path: false)
end
+ expose :avatar_path, if: ->(user, options) { options.fetch(:only_path, false) && user.avatar_path }
+
expose :web_url do |user, options|
Gitlab::Routing.url_helpers.user_url(user)
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 14a4fc6f025..fa222bf2b1c 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -367,15 +367,16 @@ module API
post ":id/fork/:forked_from_id" do
authenticated_as_admin!
- forked_from_project = find_project!(params[:forked_from_id])
- not_found!("Source Project") unless forked_from_project
+ fork_from_project = find_project!(params[:forked_from_id])
- if user_project.forked_from_project.nil?
- user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+ not_found!("Source Project") unless fork_from_project
- ::Projects::ForksCountService.new(forked_from_project).refresh_cache
+ result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
+
+ if result
+ present user_project.reload, with: Entities::Project
else
- render_api_error!("Project already forked", 409)
+ render_api_error!("Project already forked", 409) if user_project.forked?
end
end
@@ -383,11 +384,11 @@ module API
delete ":id/fork" do
authorize! :remove_fork_project, user_project
- if user_project.forked?
- destroy_conditionally!(user_project.forked_project_link)
- else
- not_modified!
+ result = destroy_conditionally!(user_project) do
+ ::Projects::UnlinkForkService.new(user_project, current_user).execute
end
+
+ result ? status(204) : not_modified!
end
desc 'Share the project with a group' do
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index b5021e8a712..614822509f0 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -39,10 +39,10 @@ module API
end
params do
requires :name, type: String, desc: 'The name of the protected branch'
- optional :push_access_level, type: Integer, default: Gitlab::Access::MASTER,
+ optional :push_access_level, type: Integer,
values: ProtectedRefAccess::ALLOWED_ACCESS_LEVELS,
desc: 'Access levels allowed to push (defaults: `40`, master access level)'
- optional :merge_access_level, type: Integer, default: Gitlab::Access::MASTER,
+ optional :merge_access_level, type: Integer,
values: ProtectedRefAccess::ALLOWED_ACCESS_LEVELS,
desc: 'Access levels allowed to merge (defaults: `40`, master access level)'
end
@@ -52,15 +52,13 @@ module API
conflict!("Protected branch '#{params[:name]}' already exists")
end
- protected_branch_params = {
- name: params[:name],
- push_access_levels_attributes: [{ access_level: params[:push_access_level] }],
- merge_access_levels_attributes: [{ access_level: params[:merge_access_level] }]
- }
+ # Replace with `declared(params)` after updating to grape v1.0.2
+ # See https://github.com/ruby-grape/grape/pull/1710
+ # and https://gitlab.com/gitlab-org/gitlab-ce/issues/40843
+ declared_params = params.slice("name", "push_access_level", "merge_access_level", "allowed_to_push", "allowed_to_merge")
- service_args = [user_project, current_user, protected_branch_params]
-
- protected_branch = ::ProtectedBranches::CreateService.new(*service_args).execute
+ api_service = ::ProtectedBranches::ApiService.new(user_project, current_user, declared_params)
+ protected_branch = api_service.create
if protected_branch.persisted?
present protected_branch, with: Entities::ProtectedBranch, project: user_project
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index b6d273b98c2..2a04c03919d 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -193,12 +193,9 @@ module Backup
end
def empty_repo?(project_or_wiki)
- project_or_wiki.repository.expire_exists_cache # protect backups from stale cache
- project_or_wiki.repository.empty_repo?
- rescue => e
- progress.puts "Ignoring repository error and continuing backing up project: #{display_repo_path(project_or_wiki)} - #{e.message}".color(:orange)
-
- false
+ # Protect against stale caches
+ project_or_wiki.repository.expire_emptiness_caches
+ project_or_wiki.repository.empty?
end
def repository_storage_paths_args
diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb
index e2b57adf611..d8fb7705b2a 100644
--- a/lib/banzai/cross_project_reference.rb
+++ b/lib/banzai/cross_project_reference.rb
@@ -11,7 +11,7 @@ module Banzai
# ref - String reference.
#
# Returns a Project, or nil if the reference can't be found
- def project_from_ref(ref)
+ def parent_from_ref(ref)
return context[:project] unless ref
Project.find_by_full_path(ref)
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 8975395aff1..e7e6a90b5fd 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -82,9 +82,9 @@ module Banzai
end
end
- def project_from_ref_cached(ref)
- cached_call(:banzai_project_refs, ref) do
- project_from_ref(ref)
+ def from_ref_cached(ref)
+ cached_call("banzai_#{parent_type}_refs".to_sym, ref) do
+ parent_from_ref(ref)
end
end
@@ -153,15 +153,20 @@ module Banzai
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text, pattern, link_content: nil, link_reference: false)
references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches|
- project_path = full_project_path(namespace_ref, project_ref)
- project = project_from_ref_cached(project_path)
+ parent_path = if parent_type == :group
+ full_group_path(namespace_ref)
+ else
+ full_project_path(namespace_ref, project_ref)
+ end
- if project
+ parent = from_ref_cached(parent_path)
+
+ if parent
object =
if link_reference
- find_object_from_link_cached(project, id)
+ find_object_from_link_cached(parent, id)
else
- find_object_cached(project, id)
+ find_object_cached(parent, id)
end
end
@@ -169,13 +174,13 @@ module Banzai
title = object_link_title(object)
klass = reference_class(object_sym)
- data = data_attributes_for(link_content || match, project, object, link: !!link_content)
+ data = data_attributes_for(link_content || match, parent, object, link: !!link_content)
url =
if matches.names.include?("url") && matches[:url]
matches[:url]
else
- url_for_object_cached(object, project)
+ url_for_object_cached(object, parent)
end
content = link_content || object_link_text(object, matches)
@@ -224,17 +229,24 @@ module Banzai
# Returns a Hash containing all object references (e.g. issue IDs) per the
# project they belong to.
- def references_per_project
- @references_per_project ||= begin
+ def references_per_parent
+ @references_per ||= {}
+
+ @references_per[parent_type] ||= begin
refs = Hash.new { |hash, key| hash[key] = Set.new }
regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
nodes.each do |node|
node.to_html.scan(regex) do
- project_path = full_project_path($~[:namespace], $~[:project])
+ path = if parent_type == :project
+ full_project_path($~[:namespace], $~[:project])
+ else
+ full_group_path($~[:group])
+ end
+
symbol = $~[object_sym]
- refs[project_path] << symbol if object_class.reference_valid?(symbol)
+ refs[path] << symbol if object_class.reference_valid?(symbol)
end
end
@@ -244,35 +256,41 @@ module Banzai
# Returns a Hash containing referenced projects grouped per their full
# path.
- def projects_per_reference
- @projects_per_reference ||= begin
+ def parent_per_reference
+ @per_reference ||= {}
+
+ @per_reference[parent_type] ||= begin
refs = Set.new
- references_per_project.each do |project_ref, _|
- refs << project_ref
+ references_per_parent.each do |ref, _|
+ refs << ref
end
- find_projects_for_paths(refs.to_a).index_by(&:full_path)
+ find_for_paths(refs.to_a).index_by(&:full_path)
end
end
- def projects_relation_for_paths(paths)
- Project.where_full_path_in(paths).includes(:namespace)
+ def relation_for_paths(paths)
+ klass = parent_type.to_s.camelize.constantize
+ result = klass.where_full_path_in(paths)
+ return result if parent_type == :group
+
+ result.includes(:namespace) if parent_type == :project
end
# Returns projects for the given paths.
- def find_projects_for_paths(paths)
+ def find_for_paths(paths)
if RequestStore.active?
- cache = project_refs_cache
+ cache = refs_cache
to_query = paths - cache.keys
unless to_query.empty?
- projects = projects_relation_for_paths(to_query)
+ records = relation_for_paths(to_query)
found = []
- projects.each do |project|
- ref = project.full_path
- get_or_set_cache(cache, ref) { project }
+ records.each do |record|
+ ref = record.full_path
+ get_or_set_cache(cache, ref) { record }
found << ref
end
@@ -284,33 +302,37 @@ module Banzai
cache.slice(*paths).values.compact
else
- projects_relation_for_paths(paths)
+ relation_for_paths(paths)
end
end
- def current_project_path
- return unless project
-
- @current_project_path ||= project.full_path
+ def current_parent_path
+ @current_parent_path ||= parent&.full_path
end
def current_project_namespace_path
- return unless project
-
- @current_project_namespace_path ||= project.namespace.full_path
+ @current_project_namespace_path ||= project&.namespace&.full_path
end
private
def full_project_path(namespace, project_ref)
- return current_project_path unless project_ref
+ return current_parent_path unless project_ref
namespace_ref = namespace || current_project_namespace_path
"#{namespace_ref}/#{project_ref}"
end
- def project_refs_cache
- RequestStore[:banzai_project_refs] ||= {}
+ def refs_cache
+ RequestStore["banzai_#{parent_type}_refs".to_sym] ||= {}
+ end
+
+ def parent_type
+ :project
+ end
+
+ def parent
+ parent_type == :project ? project : group
end
end
end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 714e0319025..eedb95197aa 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -22,10 +22,30 @@ module Banzai
end
end
+ def referenced_merge_request_commit_shas
+ return [] unless noteable.is_a?(MergeRequest)
+
+ @referenced_merge_request_commit_shas ||= begin
+ referenced_shas = references_per_parent.values.reduce(:|).to_a
+ noteable.all_commit_shas.select do |sha|
+ referenced_shas.any? { |ref| Gitlab::Git.shas_eql?(sha, ref) }
+ end
+ end
+ end
+
def url_for_object(commit, project)
h = Gitlab::Routing.url_helpers
- h.project_commit_url(project, commit,
- only_path: context[:only_path])
+
+ if referenced_merge_request_commit_shas.include?(commit.id)
+ h.diffs_project_merge_request_url(project,
+ noteable,
+ commit_id: commit.id,
+ only_path: only_path?)
+ else
+ h.project_commit_url(project,
+ commit,
+ only_path: only_path?)
+ end
end
def object_link_text_extras(object, matches)
@@ -38,6 +58,16 @@ module Banzai
extras
end
+
+ private
+
+ def noteable
+ context[:noteable]
+ end
+
+ def only_path?
+ context[:only_path]
+ end
end
end
end
diff --git a/lib/banzai/filter/epic_reference_filter.rb b/lib/banzai/filter/epic_reference_filter.rb
new file mode 100644
index 00000000000..265924abe24
--- /dev/null
+++ b/lib/banzai/filter/epic_reference_filter.rb
@@ -0,0 +1,12 @@
+module Banzai
+ module Filter
+ # The actual filter is implemented in the EE mixin
+ class EpicReferenceFilter < IssuableReferenceFilter
+ self.reference_type = :epic
+
+ def self.object_class
+ Epic
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/issuable_reference_filter.rb b/lib/banzai/filter/issuable_reference_filter.rb
new file mode 100644
index 00000000000..7addf09be73
--- /dev/null
+++ b/lib/banzai/filter/issuable_reference_filter.rb
@@ -0,0 +1,31 @@
+module Banzai
+ module Filter
+ class IssuableReferenceFilter < AbstractReferenceFilter
+ def records_per_parent
+ @records_per_project ||= {}
+
+ @records_per_project[object_class.to_s.underscore] ||= begin
+ hash = Hash.new { |h, k| h[k] = {} }
+
+ parent_per_reference.each do |path, parent|
+ record_ids = references_per_parent[path]
+
+ parent_records(parent, record_ids).each do |record|
+ hash[parent][record.iid.to_i] = record
+ end
+ end
+
+ hash
+ end
+ end
+
+ def find_object(parent, iid)
+ records_per_parent[parent][iid]
+ end
+
+ def parent_from_ref(ref)
+ parent_per_reference[ref || current_parent_path]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index ce1ab977d3b..6877cae8c55 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -8,46 +8,24 @@ module Banzai
# When external issues tracker like Jira is activated we should not
# use issue reference pattern, but we should still be able
# to reference issues from other GitLab projects.
- class IssueReferenceFilter < AbstractReferenceFilter
+ class IssueReferenceFilter < IssuableReferenceFilter
self.reference_type = :issue
def self.object_class
Issue
end
- def find_object(project, iid)
- issues_per_project[project][iid]
- end
-
def url_for_object(issue, project)
IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path], internal: true)
end
- def project_from_ref(ref)
- projects_per_reference[ref || current_project_path]
- end
-
- # Returns a Hash containing the issues per Project instance.
- def issues_per_project
- @issues_per_project ||= begin
- hash = Hash.new { |h, k| h[k] = {} }
-
- projects_per_reference.each do |path, project|
- issue_ids = references_per_project[path]
- issues = project.issues.where(iid: issue_ids.to_a)
-
- issues.each do |issue|
- hash[project][issue.iid.to_i] = issue
- end
- end
-
- hash
- end
- end
-
def projects_relation_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
+
+ def parent_records(parent, ids)
+ parent.issues.where(iid: ids.to_a)
+ end
end
end
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 5364984c9d3..d5360ad8f68 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -33,7 +33,7 @@ module Banzai
end
def find_label(project_ref, label_id, label_name)
- project = project_from_ref(project_ref)
+ project = parent_from_ref(project_ref)
return unless project
label_params = label_params(label_id, label_name)
@@ -66,7 +66,7 @@ module Banzai
def object_link_text(object, matches)
project_path = full_project_path(matches[:namespace], matches[:project])
- project_from_ref = project_from_ref_cached(project_path)
+ project_from_ref = from_ref_cached(project_path)
reference = project_from_ref.to_human_reference(project)
label_suffix = " <i>in #{reference}</i>" if reference.present?
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 0eab865ac04..b3cfa97d0e0 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -4,48 +4,19 @@ module Banzai
# to merge requests that do not exist are ignored.
#
# This filter supports cross-project references.
- class MergeRequestReferenceFilter < AbstractReferenceFilter
+ class MergeRequestReferenceFilter < IssuableReferenceFilter
self.reference_type = :merge_request
def self.object_class
MergeRequest
end
- def find_object(project, iid)
- merge_requests_per_project[project][iid]
- end
-
def url_for_object(mr, project)
h = Gitlab::Routing.url_helpers
h.project_merge_request_url(project, mr,
only_path: context[:only_path])
end
- def project_from_ref(ref)
- projects_per_reference[ref || current_project_path]
- end
-
- # Returns a Hash containing the merge_requests per Project instance.
- def merge_requests_per_project
- @merge_requests_per_project ||= begin
- hash = Hash.new { |h, k| h[k] = {} }
-
- projects_per_reference.each do |path, project|
- merge_request_ids = references_per_project[path]
-
- merge_requests = project.merge_requests
- .where(iid: merge_request_ids.to_a)
- .includes(target_project: :namespace)
-
- merge_requests.each do |merge_request|
- hash[project][merge_request.iid.to_i] = merge_request
- end
- end
-
- hash
- end
- end
-
def object_link_text_extras(object, matches)
extras = super
@@ -61,6 +32,12 @@ module Banzai
extras
end
+
+ def parent_records(parent, ids)
+ parent.merge_requests
+ .where(iid: ids.to_a)
+ .includes(target_project: :namespace)
+ end
end
end
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index bb5da310e09..2a6b0964ac5 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -38,7 +38,7 @@ module Banzai
def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
project_path = full_project_path(namespace_ref, project_ref)
- project = project_from_ref(project_path)
+ project = parent_from_ref(project_path)
return unless project
diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
index 09844931be5..d64f9ac4eb6 100644
--- a/lib/banzai/filter/upload_link_filter.rb
+++ b/lib/banzai/filter/upload_link_filter.rb
@@ -8,7 +8,7 @@ module Banzai
#
class UploadLinkFilter < HTML::Pipeline::Filter
def call
- return doc unless project
+ return doc unless project || group
doc.xpath('descendant-or-self::a[starts-with(@href, "/uploads/")]').each do |el|
process_link_attr el.attribute('href')
@@ -28,13 +28,27 @@ module Banzai
end
def build_url(uri)
- File.join(Gitlab.config.gitlab.url, project.full_path, uri)
+ base_path = Gitlab.config.gitlab.url
+
+ if group
+ urls = Gitlab::Routing.url_helpers
+ # we need to get last 2 parts of the uri which are secret and filename
+ uri_parts = uri.split(File::SEPARATOR)
+ file_path = urls.show_group_uploads_path(group, uri_parts[-2], uri_parts[-1])
+ File.join(base_path, file_path)
+ else
+ File.join(base_path, project.full_path, uri)
+ end
end
def project
context[:project]
end
+ def group
+ context[:group]
+ end
+
# Ensure that a :project key exists in context
#
# Note that while the key might exist, its value could be nil!
diff --git a/lib/banzai/issuable_extractor.rb b/lib/banzai/issuable_extractor.rb
index cbabf9156de..49603d0b363 100644
--- a/lib/banzai/issuable_extractor.rb
+++ b/lib/banzai/issuable_extractor.rb
@@ -28,8 +28,8 @@ module Banzai
issue_parser = Banzai::ReferenceParser::IssueParser.new(project, user)
merge_request_parser = Banzai::ReferenceParser::MergeRequestParser.new(project, user)
- issuables_for_nodes = issue_parser.issues_for_nodes(nodes).merge(
- merge_request_parser.merge_requests_for_nodes(nodes)
+ issuables_for_nodes = issue_parser.records_for_nodes(nodes).merge(
+ merge_request_parser.records_for_nodes(nodes)
)
# The project for the issue/MR might be pending for deletion!
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index ecb3affbba5..2691be81623 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -17,11 +17,11 @@ module Banzai
# project - A Project to use for redacting Markdown.
# user - The user viewing the Markdown/HTML documents, if any.
- # context - A Hash containing extra attributes to use during redaction
+ # redaction_context - A Hash containing extra attributes to use during redaction
def initialize(project, user = nil, redaction_context = {})
@project = project
@user = user
- @redaction_context = redaction_context
+ @redaction_context = base_context.merge(redaction_context)
end
# Renders and redacts an Array of objects.
@@ -73,19 +73,19 @@ module Banzai
# Returns a Banzai context for the given object and attribute.
def context_for(object, attribute)
- base_context.merge(object.banzai_render_context(attribute))
+ @redaction_context.merge(object.banzai_render_context(attribute))
end
def base_context
- @base_context ||= @redaction_context.merge(
+ {
current_user: user,
project: project,
skip_redaction: true
- )
+ }
end
def save_options
- return {} unless base_context[:xhtml]
+ return {} unless @redaction_context[:xhtml]
{ save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML }
end
diff --git a/lib/banzai/reference_parser/epic_parser.rb b/lib/banzai/reference_parser/epic_parser.rb
new file mode 100644
index 00000000000..08b8a4c9a0f
--- /dev/null
+++ b/lib/banzai/reference_parser/epic_parser.rb
@@ -0,0 +1,12 @@
+module Banzai
+ module ReferenceParser
+ # The actual parser is implemented in the EE mixin
+ class EpicParser < IssuableParser
+ self.reference_type = :epic
+
+ def records_for_nodes(_nodes)
+ {}
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/issuable_parser.rb b/lib/banzai/reference_parser/issuable_parser.rb
new file mode 100644
index 00000000000..3953867eb83
--- /dev/null
+++ b/lib/banzai/reference_parser/issuable_parser.rb
@@ -0,0 +1,25 @@
+module Banzai
+ module ReferenceParser
+ class IssuableParser < BaseParser
+ def nodes_visible_to_user(user, nodes)
+ records = records_for_nodes(nodes)
+
+ nodes.select do |node|
+ issuable = records[node]
+
+ issuable && can_read_reference?(user, issuable)
+ end
+ end
+
+ def referenced_by(nodes)
+ records = records_for_nodes(nodes)
+
+ nodes.map { |node| records[node] }.compact.uniq
+ end
+
+ def can_read_reference?(user, issuable)
+ can?(user, "read_#{issuable.class.to_s.underscore}".to_sym, issuable)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index e0a8ca653cb..38d4e3f3e44 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -1,10 +1,10 @@
module Banzai
module ReferenceParser
- class IssueParser < BaseParser
+ class IssueParser < IssuableParser
self.reference_type = :issue
def nodes_visible_to_user(user, nodes)
- issues = issues_for_nodes(nodes)
+ issues = records_for_nodes(nodes)
readable_issues = Ability
.issues_readable_by_user(issues.values, user).to_set
@@ -14,13 +14,7 @@ module Banzai
end
end
- def referenced_by(nodes)
- issues = issues_for_nodes(nodes)
-
- nodes.map { |node| issues[node] }.compact.uniq
- end
-
- def issues_for_nodes(nodes)
+ def records_for_nodes(nodes)
@issues_for_nodes ||= grouped_objects_for_nodes(
nodes,
Issue.all.includes(
diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb
index 75cbc7fdac4..a370ff5b5b3 100644
--- a/lib/banzai/reference_parser/merge_request_parser.rb
+++ b/lib/banzai/reference_parser/merge_request_parser.rb
@@ -1,25 +1,9 @@
module Banzai
module ReferenceParser
- class MergeRequestParser < BaseParser
+ class MergeRequestParser < IssuableParser
self.reference_type = :merge_request
- def nodes_visible_to_user(user, nodes)
- merge_requests = merge_requests_for_nodes(nodes)
-
- nodes.select do |node|
- merge_request = merge_requests[node]
-
- merge_request && can?(user, :read_merge_request, merge_request.project)
- end
- end
-
- def referenced_by(nodes)
- merge_requests = merge_requests_for_nodes(nodes)
-
- nodes.map { |node| merge_requests[node] }.compact.uniq
- end
-
- def merge_requests_for_nodes(nodes)
+ def records_for_nodes(nodes)
@merge_requests_for_nodes ||= grouped_objects_for_nodes(
nodes,
MergeRequest.includes(
@@ -40,10 +24,6 @@ module Banzai
self.class.data_attribute
)
end
-
- def can_read_reference?(user, ref_project, node)
- can?(user, :read_merge_request, ref_project)
- end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb
index d5e17a123df..d19a2519803 100644
--- a/lib/gitlab/ci/pipeline/chain/create.rb
+++ b/lib/gitlab/ci/pipeline/chain/create.rb
@@ -17,11 +17,27 @@ module Gitlab
end
rescue ActiveRecord::RecordInvalid => e
error("Failed to persist the pipeline: #{e}")
+ ensure
+ if pipeline.builds.where(stage_id: nil).any?
+ invalid_builds_counter.increment(node: hostname)
+ end
end
def break?
!pipeline.persisted?
end
+
+ private
+
+ def invalid_builds_counter
+ @counter ||= Gitlab::Metrics
+ .counter(:gitlab_ci_invalid_builds_total,
+ 'Invalid builds without stage assigned counter')
+ end
+
+ def hostname
+ @hostname ||= Socket.gethostname
+ end
end
end
end
diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb
index c98eefbce25..88e0db830f6 100644
--- a/lib/gitlab/diff/diff_refs.rb
+++ b/lib/gitlab/diff/diff_refs.rb
@@ -13,9 +13,9 @@ module Gitlab
def ==(other)
other.is_a?(self.class) &&
- shas_equal?(base_sha, other.base_sha) &&
- shas_equal?(start_sha, other.start_sha) &&
- shas_equal?(head_sha, other.head_sha)
+ Git.shas_eql?(base_sha, other.base_sha) &&
+ Git.shas_eql?(start_sha, other.start_sha) &&
+ Git.shas_eql?(head_sha, other.head_sha)
end
alias_method :eql?, :==
@@ -47,22 +47,6 @@ module Gitlab
CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
end
end
-
- private
-
- def shas_equal?(sha1, sha2)
- return true if sha1 == sha2
- return false if sha1.nil? || sha2.nil?
- return false unless sha1.class == sha2.class
-
- length = [sha1.length, sha2.length].min
-
- # If either of the shas is below the minimum length, we cannot be sure
- # that they actually refer to the same commit because of hash collision.
- return false if length < Commit::MIN_SHA_LENGTH
-
- sha1[0, length] == sha2[0, length]
- end
end
end
end
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index 2d7b57120a6..54783a07919 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -70,7 +70,7 @@ module Gitlab
def find_changed_line_pairs(lines)
# Prefixes of all diff lines, indicating their types
# For example: `" - + -+ ---+++ --+ -++"`
- line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
+ line_prefixes = lines.each_with_object("") { |line, s| s << (line[0] || ' ') }.gsub(/[^ +-]/, ' ')
changed_line_pairs = []
line_prefixes.scan(LINE_PAIRS_PATTERN) do
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 1f31cdbc96d..1f7c35cafaa 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -70,6 +70,18 @@ module Gitlab
def diff_line_code(file_path, new_line_position, old_line_position)
"#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}"
end
+
+ def shas_eql?(sha1, sha2)
+ return false if sha1.nil? || sha2.nil?
+ return false unless sha1.class == sha2.class
+
+ # If either of the shas is below the minimum length, we cannot be sure
+ # that they actually refer to the same commit because of hash collision.
+ length = [sha1.length, sha2.length].min
+ return false if length < Gitlab::Git::Commit::MIN_SHA_LENGTH
+
+ sha1[0, length] == sha2[0, length]
+ end
end
end
end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 8900e2d7afe..e90b158fb34 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -6,6 +6,7 @@ module Gitlab
attr_accessor :raw_commit, :head
+ MIN_SHA_LENGTH = 7
SERIALIZE_KEYS = [
:id, :message, :parent_ids,
:authored_date, :author_name, :author_email,
diff --git a/lib/gitlab/git/operation_service.rb b/lib/gitlab/git/operation_service.rb
index e36d5410431..7e8fe173056 100644
--- a/lib/gitlab/git/operation_service.rb
+++ b/lib/gitlab/git/operation_service.rb
@@ -83,7 +83,7 @@ module Gitlab
Gitlab::Git.check_namespace!(start_repository)
start_repository = RemoteRepository.new(start_repository) unless start_repository.is_a?(RemoteRepository)
- start_branch_name = nil if start_repository.empty_repo?
+ start_branch_name = nil if start_repository.empty?
if start_branch_name && !start_repository.branch_exists?(start_branch_name)
raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.relative_path}"
diff --git a/lib/gitlab/git/remote_repository.rb b/lib/gitlab/git/remote_repository.rb
index 3685aa20669..6bd6e58feeb 100644
--- a/lib/gitlab/git/remote_repository.rb
+++ b/lib/gitlab/git/remote_repository.rb
@@ -24,10 +24,12 @@ module Gitlab
@path = repository.path
end
- def empty_repo?
+ def empty?
# We will override this implementation in gitaly-ruby because we cannot
# use '@repository' there.
- @repository.empty_repo?
+ #
+ # Caches and memoization used on the Rails side
+ !@repository.exists? || @repository.empty?
end
def commit_id(revision)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 1468069a991..73889328f36 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -75,9 +75,6 @@ module Gitlab
@attributes = Gitlab::Git::Attributes.new(path)
end
- delegate :empty?,
- to: :rugged
-
def ==(other)
path == other.path
end
@@ -206,6 +203,13 @@ module Gitlab
end
end
+ # Git repository can contains some hidden refs like:
+ # /refs/notes/*
+ # /refs/git-as-svn/*
+ # /refs/pulls/*
+ # This refs by default not visible in project page and not cloned to client side.
+ alias_method :has_visible_content?, :has_local_branches?
+
def has_local_branches_rugged?
rugged.branches.each(:local).any? do |ref|
begin
@@ -1004,7 +1008,7 @@ module Gitlab
Gitlab::Git.check_namespace!(start_repository)
start_repository = RemoteRepository.new(start_repository) unless start_repository.is_a?(RemoteRepository)
- return yield nil if start_repository.empty_repo?
+ return yield nil if start_repository.empty?
if start_repository.same_repository?(self)
yield commit(start_branch_name)
@@ -1120,24 +1124,8 @@ module Gitlab
Gitlab::Git::Commit.find(self, ref)
end
- # Refactoring aid; allows us to copy code from app/models/repository.rb
- def empty_repo?
- !exists? || !has_visible_content?
- end
-
- #
- # Git repository can contains some hidden refs like:
- # /refs/notes/*
- # /refs/git-as-svn/*
- # /refs/pulls/*
- # This refs by default not visible in project page and not cloned to client side.
- #
- # This method return true if repository contains some content visible in project page.
- #
- def has_visible_content?
- return @has_visible_content if defined?(@has_visible_content)
-
- @has_visible_content = has_local_branches?
+ def empty?
+ !has_visible_content?
end
# Like all public `Gitlab::Git::Repository` methods, this method is part
@@ -1172,9 +1160,15 @@ module Gitlab
end
def fsck
- output, status = run_git(%W[--git-dir=#{path} fsck], nice: true)
+ gitaly_migrate(:git_fsck) do |is_enabled|
+ msg, status = if is_enabled
+ gitaly_fsck
+ else
+ shell_fsck
+ end
- raise GitError.new("Could not fsck repository:\n#{output}") unless status.zero?
+ raise GitError.new("Could not fsck repository: #{msg}") unless status.zero?
+ end
end
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
@@ -1322,6 +1316,14 @@ module Gitlab
File.write(File.join(worktree_info_path, 'sparse-checkout'), files)
end
+ def gitaly_fsck
+ gitaly_repository_client.fsck
+ end
+
+ def shell_fsck
+ run_git(%W[--git-dir=#{path} fsck], nice: true)
+ end
+
def rugged_fetch_source_branch(source_repository, source_branch, local_ref)
with_repo_branch_commit(source_repository, source_branch) do |commit|
if commit
diff --git a/lib/gitlab/git/storage/checker.rb b/lib/gitlab/git/storage/checker.rb
new file mode 100644
index 00000000000..de63cb4b40c
--- /dev/null
+++ b/lib/gitlab/git/storage/checker.rb
@@ -0,0 +1,98 @@
+module Gitlab
+ module Git
+ module Storage
+ class Checker
+ include CircuitBreakerSettings
+
+ attr_reader :storage_path, :storage, :hostname, :logger
+
+ def self.check_all(logger = Rails.logger)
+ threads = Gitlab.config.repositories.storages.keys.map do |storage_name|
+ Thread.new do
+ Thread.current[:result] = new(storage_name, logger).check_with_lease
+ end
+ end
+
+ threads.map do |thread|
+ thread.join
+ thread[:result]
+ end
+ end
+
+ def initialize(storage, logger = Rails.logger)
+ @storage = storage
+ config = Gitlab.config.repositories.storages[@storage]
+ @storage_path = config['path']
+ @logger = logger
+
+ @hostname = Gitlab::Environment.hostname
+ end
+
+ def check_with_lease
+ lease_key = "storage_check:#{cache_key}"
+ lease = Gitlab::ExclusiveLease.new(lease_key, timeout: storage_timeout)
+ result = { storage: storage, success: nil }
+
+ if uuid = lease.try_obtain
+ result[:success] = check
+
+ Gitlab::ExclusiveLease.cancel(lease_key, uuid)
+ else
+ logger.warn("#{hostname}: #{storage}: Skipping check, previous check still running")
+ end
+
+ result
+ end
+
+ def check
+ if Gitlab::Git::Storage::ForkedStorageCheck.storage_available?(storage_path, storage_timeout, access_retries)
+ track_storage_accessible
+ true
+ else
+ track_storage_inaccessible
+ logger.error("#{hostname}: #{storage}: Not accessible.")
+ false
+ end
+ end
+
+ private
+
+ def track_storage_inaccessible
+ first_failure = current_failure_info.first_failure || Time.now
+ last_failure = Time.now
+
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.pipelined do
+ redis.hset(cache_key, :first_failure, first_failure.to_i)
+ redis.hset(cache_key, :last_failure, last_failure.to_i)
+ redis.hincrby(cache_key, :failure_count, 1)
+ redis.expire(cache_key, failure_reset_time)
+ maintain_known_keys(redis)
+ end
+ end
+ end
+
+ def track_storage_accessible
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.pipelined do
+ redis.hset(cache_key, :first_failure, nil)
+ redis.hset(cache_key, :last_failure, nil)
+ redis.hset(cache_key, :failure_count, 0)
+ maintain_known_keys(redis)
+ end
+ end
+ end
+
+ def maintain_known_keys(redis)
+ expire_time = Time.now.to_i + failure_reset_time
+ redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, expire_time, cache_key)
+ redis.zremrangebyscore(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, '-inf', Time.now.to_i)
+ end
+
+ def current_failure_info
+ FailureInfo.load(cache_key)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/storage/circuit_breaker.rb b/lib/gitlab/git/storage/circuit_breaker.rb
index 4328c0ea29b..898bb1b65be 100644
--- a/lib/gitlab/git/storage/circuit_breaker.rb
+++ b/lib/gitlab/git/storage/circuit_breaker.rb
@@ -4,22 +4,11 @@ module Gitlab
class CircuitBreaker
include CircuitBreakerSettings
- FailureInfo = Struct.new(:last_failure, :failure_count)
-
attr_reader :storage,
- :hostname,
- :storage_path
-
- delegate :last_failure, :failure_count, to: :failure_info
-
- def self.reset_all!
- Gitlab::Git::Storage.redis.with do |redis|
- all_storage_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
- redis.del(*all_storage_keys) unless all_storage_keys.empty?
- end
+ :hostname
- RequestStore.delete(:circuitbreaker_cache)
- end
+ delegate :last_failure, :failure_count, :no_failures?,
+ to: :failure_info
def self.for_storage(storage)
cached_circuitbreakers = RequestStore.fetch(:circuitbreaker_cache) do
@@ -46,9 +35,6 @@ module Gitlab
def initialize(storage, hostname)
@storage = storage
@hostname = hostname
-
- config = Gitlab.config.repositories.storages[@storage]
- @storage_path = config['path']
end
def perform
@@ -65,15 +51,6 @@ module Gitlab
failure_count > failure_count_threshold
end
- def backing_off?
- return false if no_failures?
-
- recent_failure = last_failure > failure_wait_time.seconds.ago
- too_many_failures = failure_count > backoff_threshold
-
- recent_failure && too_many_failures
- end
-
private
# The circuitbreaker can be enabled for the entire fleet using a Feature
@@ -86,88 +63,13 @@ module Gitlab
end
def failure_info
- @failure_info ||= get_failure_info
- end
-
- # Memoizing the `storage_available` call means we only do it once per
- # request when the storage is available.
- #
- # When the storage appears not available, and the memoized value is `false`
- # we might want to try again.
- def storage_available?
- return @storage_available if @storage_available
-
- if @storage_available = Gitlab::Git::Storage::ForkedStorageCheck
- .storage_available?(storage_path, storage_timeout, access_retries)
- track_storage_accessible
- else
- track_storage_inaccessible
- end
-
- @storage_available
+ @failure_info ||= FailureInfo.load(cache_key)
end
def check_storage_accessible!
if circuit_broken?
raise Gitlab::Git::Storage::CircuitOpen.new("Circuit for #{storage} is broken", failure_reset_time)
end
-
- if backing_off?
- raise Gitlab::Git::Storage::Failing.new("Backing off access to #{storage}", failure_wait_time)
- end
-
- unless storage_available?
- raise Gitlab::Git::Storage::Inaccessible.new("#{storage} not accessible", failure_wait_time)
- end
- end
-
- def no_failures?
- last_failure.blank? && failure_count == 0
- end
-
- def track_storage_inaccessible
- @failure_info = FailureInfo.new(Time.now, failure_count + 1)
-
- Gitlab::Git::Storage.redis.with do |redis|
- redis.pipelined do
- redis.hset(cache_key, :last_failure, last_failure.to_i)
- redis.hincrby(cache_key, :failure_count, 1)
- redis.expire(cache_key, failure_reset_time)
- maintain_known_keys(redis)
- end
- end
- end
-
- def track_storage_accessible
- @failure_info = FailureInfo.new(nil, 0)
-
- Gitlab::Git::Storage.redis.with do |redis|
- redis.pipelined do
- redis.hset(cache_key, :last_failure, nil)
- redis.hset(cache_key, :failure_count, 0)
- maintain_known_keys(redis)
- end
- end
- end
-
- def maintain_known_keys(redis)
- expire_time = Time.now.to_i + failure_reset_time
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, expire_time, cache_key)
- redis.zremrangebyscore(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, '-inf', Time.now.to_i)
- end
-
- def get_failure_info
- last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis|
- redis.hmget(cache_key, :last_failure, :failure_count)
- end
-
- last_failure = Time.at(last_failure.to_i) if last_failure.present?
-
- FailureInfo.new(last_failure, failure_count.to_i)
- end
-
- def cache_key
- @cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}"
end
end
end
diff --git a/lib/gitlab/git/storage/circuit_breaker_settings.rb b/lib/gitlab/git/storage/circuit_breaker_settings.rb
index 257fe8cd8f0..c9e225f187d 100644
--- a/lib/gitlab/git/storage/circuit_breaker_settings.rb
+++ b/lib/gitlab/git/storage/circuit_breaker_settings.rb
@@ -6,10 +6,6 @@ module Gitlab
application_settings.circuitbreaker_failure_count_threshold
end
- def failure_wait_time
- application_settings.circuitbreaker_failure_wait_time
- end
-
def failure_reset_time
application_settings.circuitbreaker_failure_reset_time
end
@@ -22,8 +18,12 @@ module Gitlab
application_settings.circuitbreaker_access_retries
end
- def backoff_threshold
- application_settings.circuitbreaker_backoff_threshold
+ def check_interval
+ application_settings.circuitbreaker_check_interval
+ end
+
+ def cache_key
+ @cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}"
end
private
diff --git a/lib/gitlab/git/storage/failure_info.rb b/lib/gitlab/git/storage/failure_info.rb
new file mode 100644
index 00000000000..387279c110d
--- /dev/null
+++ b/lib/gitlab/git/storage/failure_info.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module Git
+ module Storage
+ class FailureInfo
+ attr_accessor :first_failure, :last_failure, :failure_count
+
+ def self.reset_all!
+ Gitlab::Git::Storage.redis.with do |redis|
+ all_storage_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
+ redis.del(*all_storage_keys) unless all_storage_keys.empty?
+ end
+
+ RequestStore.delete(:circuitbreaker_cache)
+ end
+
+ def self.load(cache_key)
+ first_failure, last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis|
+ redis.hmget(cache_key, :first_failure, :last_failure, :failure_count)
+ end
+
+ last_failure = Time.at(last_failure.to_i) if last_failure.present?
+ first_failure = Time.at(first_failure.to_i) if first_failure.present?
+
+ new(first_failure, last_failure, failure_count.to_i)
+ end
+
+ def initialize(first_failure, last_failure, failure_count)
+ @first_failure = first_failure
+ @last_failure = last_failure
+ @failure_count = failure_count
+ end
+
+ def no_failures?
+ first_failure.blank? && last_failure.blank? && failure_count == 0
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/storage/null_circuit_breaker.rb b/lib/gitlab/git/storage/null_circuit_breaker.rb
index a12d52d295f..261c936c689 100644
--- a/lib/gitlab/git/storage/null_circuit_breaker.rb
+++ b/lib/gitlab/git/storage/null_circuit_breaker.rb
@@ -11,6 +11,9 @@ module Gitlab
# These will always have nil values
attr_reader :storage_path
+ delegate :last_failure, :failure_count, :no_failures?,
+ to: :failure_info
+
def initialize(storage, hostname, error: nil)
@storage = storage
@hostname = hostname
@@ -29,16 +32,17 @@ module Gitlab
false
end
- def last_failure
- circuit_broken? ? Time.now : nil
- end
-
- def failure_count
- circuit_broken? ? failure_count_threshold : 0
- end
-
def failure_info
- Gitlab::Git::Storage::CircuitBreaker::FailureInfo.new(last_failure, failure_count)
+ @failure_info ||=
+ if circuit_broken?
+ Gitlab::Git::Storage::FailureInfo.new(Time.now,
+ Time.now,
+ failure_count_threshold)
+ else
+ Gitlab::Git::Storage::FailureInfo.new(nil,
+ nil,
+ 0)
+ end
end
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 8998c4b1a83..9d7d921bb9c 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -166,7 +166,7 @@ module Gitlab
end
if Gitlab::Database.read_only?
- raise UnauthorizedError, ERROR_MESSAGES[:cannot_push_to_read_only]
+ raise UnauthorizedError, push_to_read_only_message
end
if deploy_key
@@ -280,5 +280,9 @@ module Gitlab
UserAccess.new(user, project: project)
end
end
+
+ def push_to_read_only_message
+ ERROR_MESSAGES[:cannot_push_to_read_only]
+ end
end
end
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 98f1f45b338..1c9477e84b2 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -19,10 +19,14 @@ module Gitlab
end
if Gitlab::Database.read_only?
- raise UnauthorizedError, ERROR_MESSAGES[:read_only]
+ raise UnauthorizedError, push_to_read_only_message
end
true
end
+
+ def push_to_read_only_message
+ ERROR_MESSAGES[:read_only]
+ end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index f27cd800bdd..b753ac46291 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -27,7 +27,7 @@ module Gitlab
end
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'.freeze
- MAXIMUM_GITALY_CALLS = 30
+ MAXIMUM_GITALY_CALLS = 35
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
MUTEX = Mutex.new
@@ -336,6 +336,12 @@ module Gitlab
s.dup.force_encoding(Encoding::ASCII_8BIT)
end
+ def self.binary_stringio(s)
+ io = StringIO.new(s || '')
+ io.set_encoding(Encoding::ASCII_8BIT)
+ io
+ end
+
def self.encode_repeated(a)
Google::Protobuf::RepeatedField.new(:bytes, a.map { |s| self.encode(s) } )
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index b9e606592d7..a477d618f63 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -87,6 +87,17 @@ module Gitlab
response.result
end
+
+ def fsck
+ request = Gitaly::FsckRequest.new(repository: @gitaly_repo)
+ response = GitalyClient.call(@storage, :repository_service, :fsck, request)
+
+ if response.error.empty?
+ return "", 0
+ else
+ return response.error.b, 1
+ end
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index c8f065f5881..337d225d081 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -18,12 +18,11 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details)
)
- strio = StringIO.new(content)
+ strio = GitalyClient.binary_stringio(content)
enum = Enumerator.new do |y|
until strio.eof?
- chunk = strio.read(MAX_MSG_SIZE)
- request.content = GitalyClient.encode(chunk)
+ request.content = strio.read(MAX_MSG_SIZE)
y.yield request
@@ -46,12 +45,11 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details)
)
- strio = StringIO.new(content)
+ strio = GitalyClient.binary_stringio(content)
enum = Enumerator.new do |y|
until strio.eof?
- chunk = strio.read(MAX_MSG_SIZE)
- request.content = GitalyClient.encode(chunk)
+ request.content = strio.read(MAX_MSG_SIZE)
y.yield request
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 436a9e9550d..f4901be9581 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -32,7 +32,7 @@ module Gitlab
def init_metrics
metrics = {}
- metrics[:sampler_duration] = Metrics.histogram(with_prefix(:sampler_duration, :seconds), 'Sampler time', {})
+ metrics[:sampler_duration] = Metrics.histogram(with_prefix(:sampler_duration, :seconds), 'Sampler time', { worker: nil })
metrics[:total_time] = Metrics.gauge(with_prefix(:gc, :time_total), 'Total GC time', labels, :livesum)
GC.stat.keys.each do |key|
metrics[key] = Metrics.gauge(with_prefix(:gc, key), to_doc_string(key), labels, :livesum)
@@ -100,9 +100,9 @@ module Gitlab
worker_no = ::Prometheus::Client::Support::Unicorn.worker_id
if worker_no
- { unicorn: worker_no }
+ { worker: worker_no }
else
- { unicorn: 'master' }
+ { worker: 'master' }
end
end
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index bc836dcc08d..9ff82d628c0 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,7 +1,7 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
- REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range directly_addressed_user).freeze
+ REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range directly_addressed_user epic).freeze
attr_accessor :project, :current_user, :author
def initialize(project, current_user = nil)
diff --git a/lib/gitlab/storage_check.rb b/lib/gitlab/storage_check.rb
new file mode 100644
index 00000000000..fe81513c9ec
--- /dev/null
+++ b/lib/gitlab/storage_check.rb
@@ -0,0 +1,11 @@
+require_relative 'storage_check/cli'
+require_relative 'storage_check/gitlab_caller'
+require_relative 'storage_check/option_parser'
+require_relative 'storage_check/response'
+
+module Gitlab
+ module StorageCheck
+ ENDPOINT = '/-/storage_check'.freeze
+ Options = Struct.new(:target, :token, :interval, :dryrun)
+ end
+end
diff --git a/lib/gitlab/storage_check/cli.rb b/lib/gitlab/storage_check/cli.rb
new file mode 100644
index 00000000000..04bf1bf1d26
--- /dev/null
+++ b/lib/gitlab/storage_check/cli.rb
@@ -0,0 +1,69 @@
+module Gitlab
+ module StorageCheck
+ class CLI
+ def self.start!(args)
+ runner = new(Gitlab::StorageCheck::OptionParser.parse!(args))
+ runner.start_loop
+ end
+
+ attr_reader :logger, :options
+
+ def initialize(options)
+ @options = options
+ @logger = Logger.new(STDOUT)
+ end
+
+ def start_loop
+ logger.info "Checking #{options.target} every #{options.interval} seconds"
+
+ if options.dryrun
+ logger.info "Dryrun, exiting..."
+ return
+ end
+
+ begin
+ loop do
+ response = GitlabCaller.new(options).call!
+ log_response(response)
+ update_settings(response)
+
+ sleep options.interval
+ end
+ rescue Interrupt
+ logger.info "Ending storage-check"
+ end
+ end
+
+ def update_settings(response)
+ previous_interval = options.interval
+
+ if response.valid?
+ options.interval = response.check_interval || previous_interval
+ end
+
+ if previous_interval != options.interval
+ logger.info "Interval changed: #{options.interval} seconds"
+ end
+ end
+
+ def log_response(response)
+ unless response.valid?
+ return logger.error("Invalid response checking nfs storage: #{response.http_response.inspect}")
+ end
+
+ if response.responsive_shards.any?
+ logger.debug("Responsive shards: #{response.responsive_shards.join(', ')}")
+ end
+
+ warnings = []
+ if response.skipped_shards.any?
+ warnings << "Skipped shards: #{response.skipped_shards.join(', ')}"
+ end
+ if response.failing_shards.any?
+ warnings << "Failing shards: #{response.failing_shards.join(', ')}"
+ end
+ logger.warn(warnings.join(' - ')) if warnings.any?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/storage_check/gitlab_caller.rb b/lib/gitlab/storage_check/gitlab_caller.rb
new file mode 100644
index 00000000000..44952b68844
--- /dev/null
+++ b/lib/gitlab/storage_check/gitlab_caller.rb
@@ -0,0 +1,39 @@
+require 'excon'
+
+module Gitlab
+ module StorageCheck
+ class GitlabCaller
+ def initialize(options)
+ @options = options
+ end
+
+ def call!
+ Gitlab::StorageCheck::Response.new(get_response)
+ rescue Errno::ECONNREFUSED, Excon::Error
+ # Server not ready, treated as invalid response.
+ Gitlab::StorageCheck::Response.new(nil)
+ end
+
+ def get_response
+ scheme, *other_parts = URI.split(@options.target)
+ socket_path = if scheme == 'unix'
+ other_parts.compact.join
+ end
+
+ connection = Excon.new(@options.target, socket: socket_path)
+ connection.post(path: Gitlab::StorageCheck::ENDPOINT,
+ headers: headers)
+ end
+
+ def headers
+ @headers ||= begin
+ headers = {}
+ headers['Content-Type'] = headers['Accept'] = 'application/json'
+ headers['TOKEN'] = @options.token if @options.token
+
+ headers
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/storage_check/option_parser.rb b/lib/gitlab/storage_check/option_parser.rb
new file mode 100644
index 00000000000..66ed7906f97
--- /dev/null
+++ b/lib/gitlab/storage_check/option_parser.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module StorageCheck
+ class OptionParser
+ def self.parse!(args)
+ # Start out with some defaults
+ options = Gitlab::StorageCheck::Options.new(nil, nil, 1, false)
+
+ parser = ::OptionParser.new do |opts|
+ opts.banner = "Usage: bin/storage_check [options]"
+
+ opts.on('-t=string', '--target string', 'URL or socket to trigger storage check') do |value|
+ options.target = value
+ end
+
+ opts.on('-T=string', '--token string', 'Health token to use') { |value| options.token = value }
+
+ opts.on('-i=n', '--interval n', ::OptionParser::DecimalInteger, 'Seconds between checks') do |value|
+ options.interval = value
+ end
+
+ opts.on('-d', '--dryrun', "Output what will be performed, but don't start the process") do |value|
+ options.dryrun = value
+ end
+ end
+ parser.parse!(args)
+
+ unless options.target
+ raise ::OptionParser::InvalidArgument.new('Provide a URI to provide checks')
+ end
+
+ if URI.parse(options.target).scheme.nil?
+ raise ::OptionParser::InvalidArgument.new('Add the scheme to the target, `unix://`, `https://` or `http://` are supported')
+ end
+
+ options
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/storage_check/response.rb b/lib/gitlab/storage_check/response.rb
new file mode 100644
index 00000000000..326ab236e3e
--- /dev/null
+++ b/lib/gitlab/storage_check/response.rb
@@ -0,0 +1,77 @@
+require 'json'
+
+module Gitlab
+ module StorageCheck
+ class Response
+ attr_reader :http_response
+
+ def initialize(http_response)
+ @http_response = http_response
+ end
+
+ def valid?
+ @http_response && (200...299).cover?(@http_response.status) &&
+ @http_response.headers['Content-Type'].include?('application/json') &&
+ parsed_response
+ end
+
+ def check_interval
+ return nil unless parsed_response
+
+ parsed_response['check_interval']
+ end
+
+ def responsive_shards
+ divided_results[:responsive_shards]
+ end
+
+ def skipped_shards
+ divided_results[:skipped_shards]
+ end
+
+ def failing_shards
+ divided_results[:failing_shards]
+ end
+
+ private
+
+ def results
+ return [] unless parsed_response
+
+ parsed_response['results']
+ end
+
+ def divided_results
+ return @divided_results if @divided_results
+
+ @divided_results = {}
+ @divided_results[:responsive_shards] = []
+ @divided_results[:skipped_shards] = []
+ @divided_results[:failing_shards] = []
+
+ results.each do |info|
+ name = info['storage']
+
+ case info['success']
+ when true
+ @divided_results[:responsive_shards] << name
+ when false
+ @divided_results[:failing_shards] << name
+ else
+ @divided_results[:skipped_shards] << name
+ end
+ end
+
+ @divided_results
+ end
+
+ def parsed_response
+ return @parsed_response if defined?(@parsed_response)
+
+ @parsed_response = JSON.parse(@http_response.body)
+ rescue JSON::JSONError
+ @parsed_response = nil
+ end
+ end
+ end
+end
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 8e688dede89..374164cbe65 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -504,13 +504,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -519,7 +519,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -528,10 +528,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -585,7 +585,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -594,13 +594,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index ef730d91c75..a79a7d1a353 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -504,13 +504,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -519,7 +519,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -528,10 +528,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -585,7 +585,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -594,13 +594,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 0a1b379b3d3..f7be343c4e1 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -504,13 +504,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -519,7 +519,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -528,10 +528,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -585,7 +585,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -594,13 +594,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 31ff4e08592..c35a3503019 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -504,13 +504,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -519,7 +519,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -528,10 +528,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -585,7 +585,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -594,13 +594,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 41fa86451f5..a0e523339db 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -504,14 +504,14 @@ msgstr "L'intégration du cluster est activée pour ce projet."
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr "L'intégration de cluster est activée pour ce projet. La désactivation de cette intégration n’affectera pas votre cluster, il coupera temporairement la connexion de GitLab à celui-ci."
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
-msgstr "Le cluster est en cours de création sur Google Container Engine…"
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
+msgstr "Le cluster est en cours de création sur Google Kubernetes Engine…"
msgid "ClusterIntegration|Cluster name"
msgstr "Nom du cluster"
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
-msgstr "Le cluster a été correctement créé sur Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
+msgstr "Le cluster a été correctement créé sur Google Kubernetes Engine"
msgid "ClusterIntegration|Copy cluster name"
msgstr "Copier le nom du cluster"
@@ -519,8 +519,8 @@ msgstr "Copier le nom du cluster"
msgid "ClusterIntegration|Create cluster"
msgstr "Créer le cluster"
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
-msgstr "Créer un nouveau cluster sur Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
+msgstr "Créer un nouveau cluster sur Google Kubernetes Engine"
msgid "ClusterIntegration|Enable cluster integration"
msgstr "Activer l’intégration du cluster"
@@ -528,11 +528,11 @@ msgstr "Activer l’intégration du cluster"
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr "ID de projet Google Cloud Platform"
-msgid "ClusterIntegration|Google Container Engine"
-msgstr "Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr "Google Kubernetes Engine"
-msgid "ClusterIntegration|Google Container Engine project"
-msgstr "Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr "Google Kubernetes Engine"
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
msgstr "En savoir plus sur %{link_to_documentation}"
@@ -585,8 +585,8 @@ msgstr "Voir les zones"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "Un problème est survenu de notre côté."
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
-msgstr "Un problème est survenu lors de la création de votre cluster sur Google Container Engine."
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
+msgstr "Un problème est survenu lors de la création de votre cluster sur Google Kubernetes Engine."
msgid "ClusterIntegration|Toggle Cluster"
msgstr "Activer/désactiver le cluster"
@@ -594,14 +594,14 @@ msgstr "Activer/désactiver le cluster"
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr "Avec un cluster associé à ce projet, vous pouvez utiliser des applications de revue, déployer vos applications, exécuter vos pipelines et bien plus encore, de manière très simple."
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
-msgstr "Votre compte doit disposer de %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr "Votre compte doit disposer de %{link_to_kubernetes_engine}"
msgid "ClusterIntegration|Zone"
msgstr "Zone"
-msgid "ClusterIntegration|access to Google Container Engine"
-msgstr "Accéder à Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr "Accéder à Google Kubernetes Engine"
msgid "ClusterIntegration|cluster"
msgstr "cluster"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2220cc72502..3ebc7859232 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -494,13 +494,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine. Refresh the page to see cluster's details"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details"
msgstr ""
msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
@@ -524,7 +524,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create cluster on Google Container Engine"
+msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Create on GKE"
@@ -551,10 +551,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Helm Tiller"
@@ -638,7 +638,7 @@ msgstr ""
msgid "ClusterIntegration|Remove integration"
msgstr ""
-msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Container Engine."
+msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine."
msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
@@ -668,7 +668,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
@@ -689,13 +689,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index 7a132aeb238..8a987129452 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -504,13 +504,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -519,7 +519,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -528,10 +528,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -585,7 +585,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -594,13 +594,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index 9045ae26c4a..8d93a936be9 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -497,13 +497,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -512,7 +512,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -521,10 +521,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -578,7 +578,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -587,13 +587,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index ab74d4cbeae..d6c1ff2deeb 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -497,13 +497,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -512,7 +512,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -521,10 +521,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -578,7 +578,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -587,13 +587,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po
index 7e33af9f747..68d1f809bb4 100644
--- a/locale/nl_NL/gitlab.po
+++ b/locale/nl_NL/gitlab.po
@@ -504,13 +504,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -519,7 +519,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -528,10 +528,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -585,7 +585,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -594,13 +594,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po
index fb4a8af7217..c48909540b1 100644
--- a/locale/pl_PL/gitlab.po
+++ b/locale/pl_PL/gitlab.po
@@ -511,13 +511,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -526,7 +526,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -535,10 +535,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -592,7 +592,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -601,13 +601,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index fa335bc819d..78e0967c3bc 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -504,14 +504,14 @@ msgstr "Integração do cluster está ativada nesse projeto."
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr "Integração do cluster está ativada para esse projeto. Desabilitar a integração não afetará seu cluster, mas desligará temporariamente a conexão do Gitlab com ele."
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
-msgstr "O cluster está sendo criado no Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
+msgstr "O cluster está sendo criado no Google Kubernetes Engine..."
msgid "ClusterIntegration|Cluster name"
msgstr "Nome do cluster"
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
-msgstr "O cluster foi criado com sucesso no Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
+msgstr "O cluster foi criado com sucesso no Google Kubernetes Engine"
msgid "ClusterIntegration|Copy cluster name"
msgstr "Copiar nome do cluster"
@@ -519,8 +519,8 @@ msgstr "Copiar nome do cluster"
msgid "ClusterIntegration|Create cluster"
msgstr "Criar cluster"
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
-msgstr "Criar novo cluster no Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
+msgstr "Criar novo cluster no Google Kubernetes Engine"
msgid "ClusterIntegration|Enable cluster integration"
msgstr "Ativar integração com o cluster"
@@ -528,11 +528,11 @@ msgstr "Ativar integração com o cluster"
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr "ID do projeto no Google Cloud Platform"
-msgid "ClusterIntegration|Google Container Engine"
-msgstr "Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr "Google Kubernetes Engine"
-msgid "ClusterIntegration|Google Container Engine project"
-msgstr "Projeto no Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr "Projeto no Google Kubernetes Engine"
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
msgstr "Leia mais sobre %{link_to_documentation}"
@@ -585,8 +585,8 @@ msgstr "Ver zonas"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "Alguma coisa deu errado do nosso lado."
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
-msgstr "Algo deu errado ao criar seu cluster no Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
+msgstr "Algo deu errado ao criar seu cluster no Google Kubernetes Engine"
msgid "ClusterIntegration|Toggle Cluster"
msgstr "Alternar cluster"
@@ -594,14 +594,14 @@ msgstr "Alternar cluster"
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr "Com um cluster associado à esse projeto, você pode usar revisão de apps, fazer deploy de suas aplicações, rodar suas pipelines e muito mais de um jeito simples."
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
-msgstr "Sua conta precisa ter %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr "Sua conta precisa ter %{link_to_kubernetes_engine}"
msgid "ClusterIntegration|Zone"
msgstr "Zona"
-msgid "ClusterIntegration|access to Google Container Engine"
-msgstr "Acesso ao Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr "Acesso ao Google Kubernetes Engine"
msgid "ClusterIntegration|cluster"
msgstr "cluster"
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index 9111f1eb3d2..b25a5d1e75b 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -511,14 +511,14 @@ msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ ÐºÐ»Ð°Ñтеров включена Ð´Ð»Ñ Ñтог
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr "Ð”Ð»Ñ Ñтого проекта включена Ð¸Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ ÐºÐ»Ð°Ñтеров. Отключение интеграции не повлиÑет на клаÑтер, но Ñоединение Ñ GitLab будет временно отключено."
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
-msgstr "СоздаетÑÑ ÐºÐ»Ð°Ñтер в Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
+msgstr "СоздаетÑÑ ÐºÐ»Ð°Ñтер в Google Kubernetes Engine..."
msgid "ClusterIntegration|Cluster name"
msgstr "Ðазвание клаÑтера"
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
-msgstr "КлаÑтер был уÑпешно Ñоздан в Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
+msgstr "КлаÑтер был уÑпешно Ñоздан в Google Kubernetes Engine"
msgid "ClusterIntegration|Copy cluster name"
msgstr "Копировать название клаÑтера"
@@ -526,8 +526,8 @@ msgstr "Копировать название клаÑтера"
msgid "ClusterIntegration|Create cluster"
msgstr "Создать клаÑтер"
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
-msgstr "Создать новый клаÑтер в Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
+msgstr "Создать новый клаÑтер в Google Kubernetes Engine"
msgid "ClusterIntegration|Enable cluster integration"
msgstr "Включить интеграцию Ñ ÐºÐ»Ð°Ñтерами"
@@ -535,11 +535,11 @@ msgstr "Включить интеграцию Ñ ÐºÐ»Ð°Ñтерами"
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr "Идентификатор проекта в Google Cloud Platform"
-msgid "ClusterIntegration|Google Container Engine"
-msgstr "Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr "Google Kubernetes Engine"
-msgid "ClusterIntegration|Google Container Engine project"
-msgstr "Проект Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr "Проект Google Kubernetes Engine"
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
msgstr "Узнайте больше на %{link_to_documentation}"
@@ -592,8 +592,8 @@ msgstr "См. зоны"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr " У Ð½Ð°Ñ Ñ‡Ñ‚Ð¾-то пошло не так."
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
-msgstr "Что-то пошло не так во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ñтера в Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
+msgstr "Что-то пошло не так во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ñтера в Google Kubernetes Engine"
msgid "ClusterIntegration|Toggle Cluster"
msgstr "Переключить КлаÑтер"
@@ -601,14 +601,14 @@ msgstr "Переключить КлаÑтер"
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr "ЕÑли привÑзать клаÑтер к Ñтому проекту, вы Ñ Ð»Ñ‘Ð³ÐºÐ¾Ñтью Ñможете иÑпользовать Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ€ÐµÐ²ÑŒÑŽ, развертывать ваши приложениÑ, запуÑкать Ñборочные линии и многое другое."
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
-msgstr "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ должна иметь %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ должна иметь %{link_to_kubernetes_engine}"
msgid "ClusterIntegration|Zone"
msgstr "Зона"
-msgid "ClusterIntegration|access to Google Container Engine"
-msgstr "доÑтуп к Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr "доÑтуп к Google Kubernetes Engine"
msgid "ClusterIntegration|cluster"
msgstr "клаÑтер"
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index 73dfe949ded..53054bdaa27 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -511,14 +511,14 @@ msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· клаÑтером увімкнена Ð´Ð»Ñ Ñ
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr "Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту увімкнена Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· клаÑтером. Ð’Ð¸ÐºÐ½ÐµÐ½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— не вплине на клаÑтер, але з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ GitLab з ним буде тимчаÑово розірване."
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
-msgstr "СтворюєтьÑÑ ÐºÐ»Ð°Ñтер в Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
+msgstr "СтворюєтьÑÑ ÐºÐ»Ð°Ñтер в Google Kubernetes Engine..."
msgid "ClusterIntegration|Cluster name"
msgstr "Ім'Ñ ÐºÐ»Ð°Ñтера"
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
-msgstr "КлаÑтер був уÑпішно Ñтворений в Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
+msgstr "КлаÑтер був уÑпішно Ñтворений в Google Kubernetes Engine"
msgid "ClusterIntegration|Copy cluster name"
msgstr "Копіювати назву клаÑтера"
@@ -526,8 +526,8 @@ msgstr "Копіювати назву клаÑтера"
msgid "ClusterIntegration|Create cluster"
msgstr "Створити клаÑтер"
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
-msgstr "Створити новий клаÑтер в Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
+msgstr "Створити новий клаÑтер в Google Kubernetes Engine"
msgid "ClusterIntegration|Enable cluster integration"
msgstr "Увімкнути інтеграцію із клаÑтерами"
@@ -535,11 +535,11 @@ msgstr "Увімкнути інтеграцію із клаÑтерами"
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr "Ідентифікатор проекту в Google Cloud Platform"
-msgid "ClusterIntegration|Google Container Engine"
-msgstr "Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr "Google Kubernetes Engine"
-msgid "ClusterIntegration|Google Container Engine project"
-msgstr "Проект Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr "Проект Google Kubernetes Engine"
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про %{link_to_documentation}"
@@ -592,8 +592,8 @@ msgstr "ПереглÑнути зони"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "ЩоÑÑŒ пішло не так з нашого боку."
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
-msgstr "ЩоÑÑŒ пішло не так під Ñ‡Ð°Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐºÐ»Ð°Ñтера в Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
+msgstr "ЩоÑÑŒ пішло не так під Ñ‡Ð°Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐºÐ»Ð°Ñтера в Google Kubernetes Engine"
msgid "ClusterIntegration|Toggle Cluster"
msgstr "Переключити КлаÑтер"
@@ -601,14 +601,14 @@ msgstr "Переключити КлаÑтер"
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr "За допомогою підключеного до цього проекту клаÑтера, ви можете викориÑтовувати Review Apps, розгортати ваші проекти, запуÑкати конвеєри збірки та багато іншого."
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
-msgstr "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ мати %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ мати %{link_to_kubernetes_engine}"
msgid "ClusterIntegration|Zone"
msgstr "Зона"
-msgid "ClusterIntegration|access to Google Container Engine"
-msgstr "доÑтуп до Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr "доÑтуп до Google Kubernetes Engine"
msgid "ClusterIntegration|cluster"
msgstr "клаÑтер"
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 3a08b6c20a2..e1bc9219908 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -497,14 +497,14 @@ msgstr "此项目已å¯ç”¨é›†ç¾¤é›†æˆã€‚"
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr "此项目已å¯ç”¨é›†ç¾¤é›†æˆã€‚ç¦ç”¨æ­¤é›†æˆä¸ä¼šå½±å“您的集群,它åªä¼šæš‚时关闭 GitLab 的连接。"
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
-msgstr "集群正在 Google Container Engine 上创建..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
+msgstr "集群正在 Google Kubernetes Engine 上创建..."
msgid "ClusterIntegration|Cluster name"
msgstr "集群å称"
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
-msgstr "集群已在 Google Container Engine 上æˆåŠŸåˆ›å»º"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
+msgstr "集群已在 Google Kubernetes Engine 上æˆåŠŸåˆ›å»º"
msgid "ClusterIntegration|Copy cluster name"
msgstr "å¤åˆ¶é›†ç¾¤å称"
@@ -512,8 +512,8 @@ msgstr "å¤åˆ¶é›†ç¾¤å称"
msgid "ClusterIntegration|Create cluster"
msgstr "创建集群"
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
-msgstr "在 Google Container Engine 上创建新集群"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
+msgstr "在 Google Kubernetes Engine 上创建新集群"
msgid "ClusterIntegration|Enable cluster integration"
msgstr "å¯ç”¨é›†ç¾¤é›†æˆ"
@@ -521,11 +521,11 @@ msgstr "å¯ç”¨é›†ç¾¤é›†æˆ"
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr "Google 云平å°é¡¹ç›®ID"
-msgid "ClusterIntegration|Google Container Engine"
-msgstr "Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr "Google Kubernetes Engine"
-msgid "ClusterIntegration|Google Container Engine project"
-msgstr "Google Container Engine 项目"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr "Google Kubernetes Engine 项目"
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
msgstr "了解详细%{link_to_documentation}"
@@ -578,8 +578,8 @@ msgstr "查看区域"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "å‘生了内部错误"
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
-msgstr "在 Google Container Engine 上创建集群时å‘生错误"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
+msgstr "在 Google Kubernetes Engine 上创建集群时å‘生错误"
msgid "ClusterIntegration|Toggle Cluster"
msgstr "切æ¢é›†ç¾¤"
@@ -587,14 +587,14 @@ msgstr "切æ¢é›†ç¾¤"
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr "使用与此项目关è”的集群,您å¯ä»¥ä½¿ç”¨å®¡é˜…应用程åºï¼Œéƒ¨ç½²åº”用程åºï¼Œè¿è¡Œæµæ°´çº¿ç­‰ç­‰ã€‚"
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
-msgstr "您的å¸æˆ·å¿…须有%{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr "您的å¸æˆ·å¿…须有%{link_to_kubernetes_engine}"
msgid "ClusterIntegration|Zone"
msgstr "区域"
-msgid "ClusterIntegration|access to Google Container Engine"
-msgstr "访问 Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr "访问 Google Kubernetes Engine"
msgid "ClusterIntegration|cluster"
msgstr "集群"
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 30d5b0c1416..b851809fc7c 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -497,13 +497,13 @@ msgstr ""
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr ""
msgid "ClusterIntegration|Cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Copy cluster name"
@@ -512,7 +512,7 @@ msgstr ""
msgid "ClusterIntegration|Create cluster"
msgstr ""
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Enable cluster integration"
@@ -521,10 +521,10 @@ msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -578,7 +578,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Toggle Cluster"
@@ -587,13 +587,13 @@ msgstr ""
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|access to Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|cluster"
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 6531330074a..b6d4ed27487 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -497,13 +497,13 @@ msgstr "此專案已經啟用å¢é›†æ•´åˆ"
msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
msgstr "此專案已啟用å¢é›†æ•´åˆã€‚ç¦æ­¢å¢é›†æ•´åˆä¸æœƒå½±éŸ¿æ‚¨çš„å¢é›†ï¼Œå®ƒåªæ˜¯æš«æ™‚關閉 GitLab 的連接。"
-msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
+msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
msgstr "在 Google 容器引擎中建立新的å¢é›†"
msgid "ClusterIntegration|Cluster name"
msgstr "å¢é›†å稱"
-msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
+msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine"
msgstr "在 Google 容器引擎上æˆåŠŸå»ºç«‹å¢é›†"
msgid "ClusterIntegration|Copy cluster name"
@@ -512,7 +512,7 @@ msgstr "複製å¢é›†å稱"
msgid "ClusterIntegration|Create cluster"
msgstr "建立å¢é›†"
-msgid "ClusterIntegration|Create new cluster on Google Container Engine"
+msgid "ClusterIntegration|Create new cluster on Google Kubernetes Engine"
msgstr "在 Google 容器引擎中建立新的å¢é›†"
msgid "ClusterIntegration|Enable cluster integration"
@@ -521,10 +521,10 @@ msgstr "å•Ÿå‹•å¢é›†æ•´åˆ"
msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr "Google 雲端專案 ID"
-msgid "ClusterIntegration|Google Container Engine"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr "Google 容器引擎"
-msgid "ClusterIntegration|Google Container Engine project"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr "Google 容器引擎專案"
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
@@ -578,8 +578,8 @@ msgstr "查看å€åŸŸ"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "內部發生了錯誤"
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
-msgstr "在 Google Container Engine 上建立å¢é›†æ™‚發生了錯誤"
+msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
+msgstr "在 Google Kubernetes Engine 上建立å¢é›†æ™‚發生了錯誤"
msgid "ClusterIntegration|Toggle Cluster"
msgstr "å¢é›†é–‹é—œ"
@@ -587,14 +587,14 @@ msgstr "å¢é›†é–‹é—œ"
msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr "當å¢é›†é€£çµåˆ°æ­¤å°ˆæ¡ˆï¼Œä½ å¯ä»¥ä½¿ç”¨è¤‡é–±æ‡‰ç”¨ (review apps),部署你的應用程å¼ï¼ŒåŸ·è¡Œä½ çš„æµæ°´ç·š (pipelines),還有更多容易上手的方å¼å¯ä»¥ä½¿ç”¨ã€‚"
-msgid "ClusterIntegration|Your account must have %{link_to_container_engine}"
-msgstr "您的帳號必須有 %{link_to_container_engine}"
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr "您的帳號必須有 %{link_to_kubernetes_engine}"
msgid "ClusterIntegration|Zone"
msgstr "å€åŸŸ"
-msgid "ClusterIntegration|access to Google Container Engine"
-msgstr "å­˜å– Google Container Engine"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr "å­˜å– Google Kubernetes Engine"
msgid "ClusterIntegration|cluster"
msgstr "å¢é›†"
diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb
index cb743a7bf11..53fdaaed078 100644
--- a/qa/qa/page/group/new.rb
+++ b/qa/qa/page/group/new.rb
@@ -4,6 +4,7 @@ module QA
class New < Page::Base
def set_path(path)
fill_in 'group_path', with: path
+ fill_in 'group_name', with: path
end
def set_description(description)
diff --git a/spec/bin/storage_check_spec.rb b/spec/bin/storage_check_spec.rb
new file mode 100644
index 00000000000..02f6fcb6e3a
--- /dev/null
+++ b/spec/bin/storage_check_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe 'bin/storage_check' do
+ it 'is executable' do
+ command = %w[bin/storage_check -t unix://the/path/to/a/unix-socket.sock -i 10 -d]
+ expected_output = 'Checking unix://the/path/to/a/unix-socket.sock every 10 seconds'
+
+ output, status = Gitlab::Popen.popen(command, Rails.root.to_s)
+
+ expect(status).to eq(0)
+ expect(output).to include(expected_output)
+ end
+end
diff --git a/spec/controllers/admin/health_check_controller_spec.rb b/spec/controllers/admin/health_check_controller_spec.rb
index 0b8e0c8a065..d15ee0021d9 100644
--- a/spec/controllers/admin/health_check_controller_spec.rb
+++ b/spec/controllers/admin/health_check_controller_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Admin::HealthCheckController, broken_storage: true do
+describe Admin::HealthCheckController do
let(:admin) { create(:admin) }
before do
@@ -17,7 +17,7 @@ describe Admin::HealthCheckController, broken_storage: true do
describe 'POST reset_storage_health' do
it 'resets all storage health information' do
- expect(Gitlab::Git::Storage::CircuitBreaker).to receive(:reset_all!)
+ expect(Gitlab::Git::Storage::FailureInfo).to receive(:reset_all!)
post :reset_storage_health
end
diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb
new file mode 100644
index 00000000000..67a11e56e94
--- /dev/null
+++ b/spec/controllers/groups/uploads_controller_spec.rb
@@ -0,0 +1,10 @@
+require 'spec_helper'
+
+describe Groups::UploadsController do
+ let(:model) { create(:group, :public) }
+ let(:params) do
+ { group_id: model }
+ end
+
+ it_behaves_like 'handle uploads'
+end
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index 9e9cf4f2c1f..95946def5f9 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -14,6 +14,48 @@ describe HealthController do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
+ describe '#storage_check' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip)
+ end
+
+ subject { post :storage_check }
+
+ it 'checks all the configured storages' do
+ expect(Gitlab::Git::Storage::Checker).to receive(:check_all).and_call_original
+
+ subject
+ end
+
+ it 'returns the check interval' do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true')
+ stub_application_setting(circuitbreaker_check_interval: 10)
+
+ subject
+
+ expect(json_response['check_interval']).to eq(10)
+ end
+
+ context 'with failing storages', :broken_storage do
+ before do
+ stub_storage_settings(
+ broken: { path: 'tmp/tests/non-existent-repositories' }
+ )
+ end
+
+ it 'includes the failure information' do
+ subject
+
+ expected_results = [
+ { 'storage' => 'broken', 'success' => false },
+ { 'storage' => 'default', 'success' => true }
+ ]
+
+ expect(json_response['results']).to eq(expected_results)
+ end
+ end
+ end
+
describe '#readiness' do
shared_context 'endpoint responding with readiness data' do
let(:request_params) { {} }
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index fd90c0d8bad..694c64ae1ad 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -132,6 +132,22 @@ describe Projects::CommitController do
expect(response).to be_success
end
end
+
+ context 'in the context of a merge_request' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:commit) { merge_request.commits.first }
+
+ it 'prepare diff notes in the context of the merge request' do
+ go(id: commit.id, merge_request_iid: merge_request.iid)
+
+ expect(assigns(:new_diff_note_attrs)).to eq({
+ noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id,
+ commit_id: commit.id
+ })
+ expect(response).to be_ok
+ end
+ end
end
describe 'GET branches' do
@@ -323,7 +339,7 @@ describe Projects::CommitController do
context 'when the commit does not exist' do
before do
- diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path)
+ diff_for_path(id: commit.id.reverse, old_path: existing_path, new_path: existing_path)
end
it 'returns a 404' do
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 18a70bec103..ba97ccfbbd4 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -100,7 +100,8 @@ describe Projects::MergeRequests::DiffsController do
expect(assigns(:diff_notes_disabled)).to be_falsey
expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest',
- noteable_id: merge_request.id)
+ noteable_id: merge_request.id,
+ commit_id: nil)
end
it 'only renders the diffs for the path given' do
diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb
index b2d83a02290..1cc488bef32 100644
--- a/spec/controllers/projects/pipelines_settings_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb
@@ -16,14 +16,13 @@ describe Projects::PipelinesSettingsController do
patch :update,
namespace_id: project.namespace.to_param,
project_id: project,
- project: { auto_devops_attributes: params,
- run_auto_devops_pipeline_implicit: 'false',
- run_auto_devops_pipeline_explicit: auto_devops_pipeline }
+ project: {
+ auto_devops_attributes: params
+ }
end
context 'when updating the auto_devops settings' do
let(:params) { { enabled: '', domain: 'mepmep.md' } }
- let(:auto_devops_pipeline) { 'false' }
it 'redirects to the settings page' do
subject
@@ -44,7 +43,9 @@ describe Projects::PipelinesSettingsController do
end
context 'when run_auto_devops_pipeline is true' do
- let(:auto_devops_pipeline) { 'true' }
+ before do
+ expect_any_instance_of(Projects::UpdateService).to receive(:run_auto_devops_pipeline?).and_return(true)
+ end
it 'queues a CreatePipelineWorker' do
expect(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args)
@@ -54,7 +55,9 @@ describe Projects::PipelinesSettingsController do
end
context 'when run_auto_devops_pipeline is not true' do
- let(:auto_devops_pipeline) { 'false' }
+ before do
+ expect_any_instance_of(Projects::UpdateService).to receive(:run_auto_devops_pipeline?).and_return(false)
+ end
it 'does not queue a CreatePipelineWorker' do
expect(CreatePipelineWorker).not_to receive(:perform_async).with(project.id, user.id, :web, any_args)
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index c2550b1efa7..d572085661d 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -1,247 +1,10 @@
-require('spec_helper')
+require 'spec_helper'
describe Projects::UploadsController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
- let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
-
- describe "POST #create" do
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
-
- context "without params['file']" do
- it "returns an error" do
- post :create,
- namespace_id: project.namespace.to_param,
- project_id: project,
- format: :json
- expect(response).to have_gitlab_http_status(422)
- end
- end
-
- context 'with valid image' do
- before do
- post :create,
- namespace_id: project.namespace.to_param,
- project_id: project,
- file: jpg,
- format: :json
- end
-
- it 'returns a content with original filename, new link, and correct type.' do
- expect(response.body).to match '\"alt\":\"rails_sample\"'
- expect(response.body).to match "\"url\":\"/uploads"
- end
-
- # NOTE: This is as close as we're getting to an Integration test for this
- # behavior. We're avoiding a proper Feature test because those should be
- # testing things entirely user-facing, which the Upload model is very much
- # not.
- it 'creates a corresponding Upload record' do
- upload = Upload.last
-
- aggregate_failures do
- expect(upload).to exist
- expect(upload.model).to eq project
- end
- end
- end
-
- context 'with valid non-image file' do
- before do
- post :create,
- namespace_id: project.namespace.to_param,
- project_id: project,
- file: txt,
- format: :json
- end
-
- it 'returns a content with original filename, new link, and correct type.' do
- expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
- expect(response.body).to match "\"url\":\"/uploads"
- end
- end
+ let(:model) { create(:project, :public) }
+ let(:params) do
+ { namespace_id: model.namespace.to_param, project_id: model }
end
- describe "GET #show" do
- let(:go) do
- get :show,
- namespace_id: project.namespace.to_param,
- project_id: project,
- secret: "123456",
- filename: "image.jpg"
- end
-
- context "when the project is public" do
- before do
- project.update_attribute(:visibility_level, Project::PUBLIC)
- end
-
- context "when not signed in" do
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
-
- context "when the file doesn't exist" do
- it "responds with status 404" do
- go
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
-
- context "when signed in" do
- before do
- sign_in(user)
- end
-
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
-
- context "when the file doesn't exist" do
- it "responds with status 404" do
- go
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
- end
-
- context "when the project is private" do
- before do
- project.update_attribute(:visibility_level, Project::PRIVATE)
- end
-
- context "when not signed in" do
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- context "when the file is an image" do
- before do
- allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
-
- context "when the file is not an image" do
- it "redirects to the sign in page" do
- go
-
- expect(response).to redirect_to(new_user_session_path)
- end
- end
- end
-
- context "when the file doesn't exist" do
- it "redirects to the sign in page" do
- go
-
- expect(response).to redirect_to(new_user_session_path)
- end
- end
- end
-
- context "when signed in" do
- before do
- sign_in(user)
- end
-
- context "when the user has access to the project" do
- before do
- project.team << [user, :master]
- end
-
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
-
- context "when the file doesn't exist" do
- it "responds with status 404" do
- go
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
-
- context "when the user doesn't have access to the project" do
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- context "when the file is an image" do
- before do
- allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
-
- context "when the file is not an image" do
- it "responds with status 404" do
- go
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
-
- context "when the file doesn't exist" do
- it "responds with status 404" do
- go
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
- end
- end
- end
+ it_behaves_like 'handle uploads'
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index ab4ae123429..471bfb3213a 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -63,13 +63,19 @@ FactoryGirl.define do
factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do
association :project, :repository
+
+ transient do
+ line_number 14
+ diff_refs { project.commit(commit_id).try(:diff_refs) }
+ end
+
position do
Gitlab::Diff::Position.new(
old_path: "files/ruby/popen.rb",
new_path: "files/ruby/popen.rb",
old_line: nil,
- new_line: 14,
- diff_refs: project.commit(commit_id).try(:diff_refs)
+ new_line: line_number,
+ diff_refs: diff_refs
)
end
end
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 3222c41c3d8..e18f1a6bd4a 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -4,5 +4,21 @@ FactoryGirl.define do
path { "uploads/-/system/project/avatar/avatar.jpg" }
size 100.kilobytes
uploader "AvatarUploader"
+
+ trait :personal_snippet do
+ model { build(:personal_snippet) }
+ uploader "PersonalFileUploader"
+ end
+
+ trait :issuable_upload do
+ path { "#{SecureRandom.hex}/myfile.jpg" }
+ uploader "FileUploader"
+ end
+
+ trait :namespace_upload do
+ path { "#{SecureRandom.hex}/myfile.jpg" }
+ model { build(:group) }
+ uploader "NamespaceFileUploader"
+ end
end
end
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index 4430fc15501..ac3392b49f9 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature "Admin Health Check", :feature, :broken_storage do
+feature "Admin Health Check", :feature do
include StubENV
before do
@@ -36,6 +36,7 @@ feature "Admin Health Check", :feature, :broken_storage do
context 'when services are up' do
before do
+ stub_storage_settings({}) # Hide the broken storage
visit admin_health_check_path
end
@@ -56,10 +57,8 @@ feature "Admin Health Check", :feature, :broken_storage do
end
end
- context 'with repository storage failures' do
+ context 'with repository storage failures', :broken_storage do
before do
- # Track a failure
- Gitlab::Git::Storage::CircuitBreaker.for_storage('broken').perform { nil } rescue nil
visit admin_health_check_path
end
@@ -67,9 +66,10 @@ feature "Admin Health Check", :feature, :broken_storage do
hostname = Gitlab::Environment.hostname
maximum_failures = Gitlab::CurrentSettings.current_application_settings
.circuitbreaker_failure_count_threshold
+ number_of_failures = maximum_failures + 1
- expect(page).to have_content('broken: failed storage access attempt on host:')
- expect(page).to have_content("#{hostname}: 1 of #{maximum_failures} failures.")
+ expect(page).to have_content("broken: #{number_of_failures} failed storage access attempts:")
+ expect(page).to have_content("#{hostname}: #{number_of_failures} of #{maximum_failures} failures.")
end
it 'allows resetting storage failures' do
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index cc1b187ff54..e285befc66f 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -207,8 +207,9 @@ describe 'GitLab Markdown' do
before do
@feat = MarkdownFeature.new
- # `markdown` helper expects a `@project` variable
+ # `markdown` helper expects a `@project` and `@group` variable
@project = @feat.project
+ @group = @feat.group
end
context 'default pipeline' do
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index 29f95039af8..482f2e51c8b 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -6,18 +6,47 @@ feature 'Merge Request versions', :js do
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
let!(:merge_request_diff2) { merge_request.merge_request_diffs.create(head_commit_sha: nil) }
let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+ let!(:params) { Hash.new }
before do
sign_in(create(:admin))
- visit diffs_project_merge_request_path(project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request, params)
end
- it 'show the latest version of the diff' do
- page.within '.mr-version-dropdown' do
- expect(page).to have_content 'latest version'
+ shared_examples 'allows commenting' do |file_id:, line_code:, comment:|
+ it do
+ diff_file_selector = ".diff-file[id='#{file_id}']"
+ line_code = "#{file_id}_#{line_code}"
+
+ page.within(diff_file_selector) do
+ find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").hover
+ find(".line_holder[id='#{line_code}'] button").click
+
+ page.within("form[data-line-code='#{line_code}']") do
+ fill_in "note[note]", with: comment
+ find(".js-comment-button").click
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content(comment)
+ end
end
+ end
- expect(page).to have_content '8 changed files'
+ describe 'compare with the latest version' do
+ it 'show the latest version of the diff' do
+ page.within '.mr-version-dropdown' do
+ expect(page).to have_content 'latest version'
+ end
+
+ expect(page).to have_content '8 changed files'
+ end
+
+ it_behaves_like 'allows commenting',
+ file_id: '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44',
+ line_code: '1_1',
+ comment: 'Typo, please fix.'
end
describe 'switch between versions' do
@@ -62,24 +91,10 @@ feature 'Merge Request versions', :js do
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
- it 'allows commenting' do
- diff_file_selector = ".diff-file[id='7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44']"
- line_code = '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_2_2'
-
- page.within(diff_file_selector) do
- find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").hover
- find(".line_holder[id='#{line_code}'] button").click
-
- page.within("form[data-line-code='#{line_code}']") do
- fill_in "note[note]", with: "Typo, please fix"
- find(".js-comment-button").click
- end
-
- wait_for_requests
-
- expect(page).to have_content("Typo, please fix")
- end
- end
+ it_behaves_like 'allows commenting',
+ file_id: '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44',
+ line_code: '2_2',
+ comment: 'Typo, please fix.'
end
describe 'compare with older version' do
@@ -132,25 +147,6 @@ feature 'Merge Request versions', :js do
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
- it 'allows commenting' do
- diff_file_selector = ".diff-file[id='7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44']"
- line_code = '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_4_4'
-
- page.within(diff_file_selector) do
- find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").hover
- find(".line_holder[id='#{line_code}'] button").click
-
- page.within("form[data-line-code='#{line_code}']") do
- fill_in "note[note]", with: "Typo, please fix"
- find(".js-comment-button").click
- end
-
- wait_for_requests
-
- expect(page).to have_content("Typo, please fix")
- end
- end
-
it 'show diff between new and old version' do
expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
end
@@ -162,6 +158,11 @@ feature 'Merge Request versions', :js do
end
expect(page).to have_content '8 changed files'
end
+
+ it_behaves_like 'allows commenting',
+ file_id: '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44',
+ line_code: '4_4',
+ comment: 'Typo, please fix.'
end
describe 'compare with same version' do
@@ -210,4 +211,24 @@ feature 'Merge Request versions', :js do
expect(page).to have_content '0 changed files'
end
end
+
+ describe 'scoped in a commit' do
+ let(:params) { { commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' } }
+
+ before do
+ wait_for_requests
+ end
+
+ it 'should only show diffs from the commit' do
+ diff_commit_ids = find_all('.diff-file [data-commit-id]').map {|diff| diff['data-commit-id']}
+
+ expect(diff_commit_ids).not_to be_empty
+ expect(diff_commit_ids).to all(eq(params[:commit_id]))
+ end
+
+ it_behaves_like 'allows commenting',
+ file_id: '2f6fcd96b88b36ce98c38da085c795a27d92a3dd',
+ line_code: '6_6',
+ comment: 'Typo, please fix.'
+ end
end
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 2bad3b02250..3ee094c216e 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -63,6 +63,18 @@ describe 'Merge request', :js do
expect(page).to have_selector('.accept-merge-request')
expect(find('.accept-merge-request')['disabled']).not_to be(true)
end
+
+ it 'allows me to merge, see cherry-pick modal and load branches list' do
+ wait_for_requests
+ click_button 'Merge'
+
+ wait_for_requests
+ click_link 'Cherry-pick'
+ page.find('.js-project-refs-dropdown').click
+ wait_for_requests
+
+ expect(page.all('.js-cherry-pick-form .dropdown-content li').size).to be > 1
+ end
end
context 'view merge request with external CI service' do
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 5a00b463960..67b8901f8fb 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -46,15 +46,15 @@ feature 'Gcp Cluster', :js do
end
it 'user sees a cluster details page and creation status' do
- expect(page).to have_content('Cluster is being created on Google Container Engine...')
+ expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...')
Clusters::Cluster.last.provider.make_created!
- expect(page).to have_content('Cluster was successfully created on Google Container Engine')
+ expect(page).to have_content('Cluster was successfully created on Google Kubernetes Engine')
end
it 'user sees a error if something worng during creation' do
- expect(page).to have_content('Cluster is being created on Google Container Engine...')
+ expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...')
Clusters::Cluster.last.provider.make_errored!('Something wrong!')
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index eb8e7265dd3..561f08cba00 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -59,107 +59,6 @@ feature "Pipelines settings" do
expect(project.auto_devops).to be_present
expect(project.auto_devops).not_to be_enabled
end
-
- describe 'Immediately run pipeline checkbox option', :js do
- context 'when auto devops is set to instance default (enabled)' do
- before do
- stub_application_setting(auto_devops_enabled: true)
- project.create_auto_devops!(enabled: nil)
- visit project_settings_ci_cd_path(project)
- end
-
- it 'does not show checkboxes on page-load' do
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false)
- end
-
- it 'selecting explicit disabled hides all checkboxes' do
- page.choose('project_auto_devops_attributes_enabled_false')
-
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false)
- end
-
- it 'selecting explicit enabled hides all checkboxes because we are already enabled' do
- page.choose('project_auto_devops_attributes_enabled_true')
-
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false)
- end
- end
-
- context 'when auto devops is set to instance default (disabled)' do
- before do
- stub_application_setting(auto_devops_enabled: false)
- project.create_auto_devops!(enabled: nil)
- visit project_settings_ci_cd_path(project)
- end
-
- it 'does not show checkboxes on page-load' do
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false)
- end
-
- it 'selecting explicit disabled hides all checkboxes' do
- page.choose('project_auto_devops_attributes_enabled_false')
-
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false)
- end
-
- it 'selecting explicit enabled shows a checkbox' do
- page.choose('project_auto_devops_attributes_enabled_true')
-
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper:not(.hide)', count: 1)
- end
- end
-
- context 'when auto devops is set to explicit disabled' do
- before do
- stub_application_setting(auto_devops_enabled: true)
- project.create_auto_devops!(enabled: false)
- visit project_settings_ci_cd_path(project)
- end
-
- it 'does not show checkboxes on page-load' do
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 2, visible: false)
- end
-
- it 'selecting explicit enabled shows a checkbox' do
- page.choose('project_auto_devops_attributes_enabled_true')
-
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper:not(.hide)', count: 1)
- end
-
- it 'selecting instance default (enabled) shows a checkbox' do
- page.choose('project_auto_devops_attributes_enabled_')
-
- expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper:not(.hide)', count: 1)
- end
- end
-
- context 'when auto devops is set to explicit enabled' do
- before do
- stub_application_setting(auto_devops_enabled: false)
- project.create_auto_devops!(enabled: true)
- visit project_settings_ci_cd_path(project)
- end
-
- it 'does not have any checkboxes' do
- expect(page).not_to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper', visible: false)
- end
- end
-
- context 'when master contains a .gitlab-ci.yml file' do
- let(:project) { create(:project, :repository) }
-
- before do
- project.repository.create_file(user, '.gitlab-ci.yml', "script: ['test']", message: 'test', branch_name: project.default_branch)
- stub_application_setting(auto_devops_enabled: true)
- project.create_auto_devops!(enabled: false)
- visit project_settings_ci_cd_path(project)
- end
-
- it 'does not have any checkboxes' do
- expect(page).not_to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper', visible: false)
- end
- end
- end
end
end
end
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index 7266e1b84d1..5e272af6073 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -82,104 +82,4 @@ describe AutoDevopsHelper do
it { is_expected.to eq(false) }
end
end
-
- describe '.show_run_auto_devops_pipeline_checkbox_for_instance_setting?' do
- subject { helper.show_run_auto_devops_pipeline_checkbox_for_instance_setting?(project) }
-
- context 'when master contains a .gitlab-ci.yml file' do
- before do
- allow(project.repository).to receive(:gitlab_ci_yml).and_return("script: ['test']")
- end
-
- it { is_expected.to eq(false) }
- end
-
- context 'when auto devops is explicitly enabled' do
- before do
- project.create_auto_devops!(enabled: true)
- end
-
- it { is_expected.to eq(false) }
- end
-
- context 'when auto devops is explicitly disabled' do
- before do
- project.create_auto_devops!(enabled: false)
- end
-
- context 'when auto devops is enabled system-wide' do
- before do
- stub_application_setting(auto_devops_enabled: true)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'when auto devops is disabled system-wide' do
- before do
- stub_application_setting(auto_devops_enabled: false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
- context 'when auto devops is set to instance setting' do
- before do
- project.create_auto_devops!(enabled: nil)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
- describe '.show_run_auto_devops_pipeline_checkbox_for_explicit_setting?' do
- subject { helper.show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(project) }
-
- context 'when master contains a .gitlab-ci.yml file' do
- before do
- allow(project.repository).to receive(:gitlab_ci_yml).and_return("script: ['test']")
- end
-
- it { is_expected.to eq(false) }
- end
-
- context 'when auto devops is explicitly enabled' do
- before do
- project.create_auto_devops!(enabled: true)
- end
-
- it { is_expected.to eq(false) }
- end
-
- context 'when auto devops is explicitly disabled' do
- before do
- project.create_auto_devops!(enabled: false)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'when auto devops is set to instance setting' do
- before do
- project.create_auto_devops!(enabled: nil)
- end
-
- context 'when auto devops is enabled system-wide' do
- before do
- stub_application_setting(auto_devops_enabled: true)
- end
-
- it { is_expected.to eq(false) }
- end
-
- context 'when auto devops is disabled system-wide' do
- before do
- stub_application_setting(auto_devops_enabled: false)
- end
-
- it { is_expected.to eq(true) }
- end
- end
- end
end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index fd7900c32f4..3008528e60c 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -1,7 +1,9 @@
require 'spec_helper'
describe MergeRequestsHelper do
+ include ActionView::Helpers::UrlHelper
include ProjectForksHelper
+
describe 'ci_build_details_path' do
let(:project) { create(:project) }
let(:merge_request) { MergeRequest.new }
@@ -41,4 +43,19 @@ describe MergeRequestsHelper do
it { is_expected.to eq([source_title, target_title]) }
end
end
+
+ describe '#tab_link_for' do
+ let(:merge_request) { create(:merge_request, :simple) }
+ let(:options) { Hash.new }
+
+ subject { tab_link_for(merge_request, :show, options) { 'Discussion' } }
+
+ describe 'supports the :force_link option' do
+ let(:options) { { force_link: true } }
+
+ it 'removes the data-toggle attributes' do
+ is_expected.not_to match(/data-toggle="tab"/)
+ end
+ end
+ end
end
diff --git a/spec/javascripts/deploy_keys/components/action_btn_spec.js b/spec/javascripts/deploy_keys/components/action_btn_spec.js
index 5b93fbc5575..7025c3d836c 100644
--- a/spec/javascripts/deploy_keys/components/action_btn_spec.js
+++ b/spec/javascripts/deploy_keys/components/action_btn_spec.js
@@ -34,7 +34,7 @@ describe('Deploy keys action btn', () => {
setTimeout(() => {
expect(
eventHub.$emit,
- ).toHaveBeenCalledWith('enable.key', deployKey);
+ ).toHaveBeenCalledWith('enable.key', deployKey, jasmine.anything());
done();
});
diff --git a/spec/javascripts/deploy_keys/components/app_spec.js b/spec/javascripts/deploy_keys/components/app_spec.js
index 700897f50b0..0ca9290d3d2 100644
--- a/spec/javascripts/deploy_keys/components/app_spec.js
+++ b/spec/javascripts/deploy_keys/components/app_spec.js
@@ -139,4 +139,18 @@ describe('Deploy keys app component', () => {
it('hasKeys returns true when there are keys', () => {
expect(vm.hasKeys).toEqual(3);
});
+
+ it('resets remove button loading state', (done) => {
+ spyOn(window, 'confirm').and.returnValue(false);
+
+ const btn = vm.$el.querySelector('.btn-warning');
+
+ btn.click();
+
+ Vue.nextTick(() => {
+ expect(btn.querySelector('.fa')).toBeNull();
+
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index 5e67911d338..20c4caa865d 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -28,7 +28,7 @@ describe('Job', () => {
});
it('copies build options', function () {
- expect(this.job.pageUrl).toBe(JOB_URL);
+ expect(this.job.pagePath).toBe(JOB_URL);
expect(this.job.buildStatus).toBe('success');
expect(this.job.buildStage).toBe('test');
expect(this.job.state).toBe('');
diff --git a/spec/javascripts/lib/utils/datefix_spec.js b/spec/javascripts/lib/utils/datefix_spec.js
index e58ac4300ba..a9f3abcf2a4 100644
--- a/spec/javascripts/lib/utils/datefix_spec.js
+++ b/spec/javascripts/lib/utils/datefix_spec.js
@@ -21,7 +21,7 @@ describe('datefix', () => {
describe('pikadayToString', () => {
it('should format a UTC date into yyyy-mm-dd format', () => {
- expect(pikadayToString(new Date('2020-01-29'))).toEqual('2020-01-29');
+ expect(pikadayToString(new Date('2020-01-29:00:00'))).toEqual('2020-01-29');
});
});
});
diff --git a/spec/lib/gitlab/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index b68301a066a..b68301a066a 100644
--- a/spec/lib/gitlab/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
new file mode 100644
index 00000000000..6ee3d531d6e
--- /dev/null
+++ b/spec/lib/backup/repository_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Backup::Repository do
+ let(:progress) { StringIO.new }
+ let!(:project) { create(:project) }
+
+ before do
+ allow(progress).to receive(:puts)
+ allow(progress).to receive(:print)
+
+ allow_any_instance_of(String).to receive(:color) do |string, _color|
+ string
+ end
+
+ allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
+ end
+
+ describe '#dump' do
+ describe 'repo failure' do
+ before do
+ allow(Gitlab::Popen).to receive(:popen).and_return(['normal output', 0])
+ end
+
+ it 'does not raise error' do
+ expect { described_class.new.dump }.not_to raise_error
+ end
+ end
+ end
+
+ describe '#restore' do
+ describe 'command failure' do
+ before do
+ allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
+ end
+
+ it 'shows the appropriate error' do
+ described_class.new.restore
+
+ expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
+ end
+ end
+ end
+
+ describe '#empty_repo?' do
+ context 'for a wiki' do
+ let(:wiki) { create(:project_wiki) }
+
+ it 'invalidates the emptiness cache' do
+ expect(wiki.repository).to receive(:expire_emptiness_caches).once
+
+ wiki.empty?
+ end
+
+ context 'wiki repo has content' do
+ let!(:wiki_page) { create(:wiki_page, wiki: wiki) }
+
+ it 'returns true, regardless of bad cache value' do
+ expect(described_class.new.send(:empty_repo?, wiki)).to be(false)
+ end
+ end
+
+ context 'wiki repo does not have content' do
+ it 'returns true, regardless of bad cache value' do
+ expect(described_class.new.send(:empty_repo?, wiki)).to be_truthy
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index d70749536b8..68ca960caab 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -3,20 +3,20 @@ require 'spec_helper'
describe Banzai::CrossProjectReference do
include described_class
- describe '#project_from_ref' do
+ describe '#parent_from_ref' do
context 'when no project was referenced' do
it 'returns the project from context' do
project = double
allow(self).to receive(:context).and_return({ project: project })
- expect(project_from_ref(nil)).to eq project
+ expect(parent_from_ref(nil)).to eq project
end
end
context 'when referenced project does not exist' do
it 'returns nil' do
- expect(project_from_ref('invalid/reference')).to be_nil
+ expect(parent_from_ref('invalid/reference')).to be_nil
end
end
@@ -27,7 +27,7 @@ describe Banzai::CrossProjectReference do
expect(Project).to receive(:find_by_full_path)
.with('cross/reference').and_return(project2)
- expect(project_from_ref('cross/reference')).to eq project2
+ expect(parent_from_ref('cross/reference')).to eq project2
end
end
end
diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
index 7c0ba9ee67f..1e82d18d056 100644
--- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
@@ -3,67 +3,67 @@ require 'spec_helper'
describe Banzai::Filter::AbstractReferenceFilter do
let(:project) { create(:project) }
- describe '#references_per_project' do
- it 'returns a Hash containing references grouped per project paths' do
+ describe '#references_per_parent' do
+ it 'returns a Hash containing references grouped per parent paths' do
doc = Nokogiri::HTML.fragment("#1 #{project.full_path}#2")
filter = described_class.new(doc, project: project)
expect(filter).to receive(:object_class).exactly(4).times.and_return(Issue)
expect(filter).to receive(:object_sym).twice.and_return(:issue)
- refs = filter.references_per_project
+ refs = filter.references_per_parent
expect(refs).to be_an_instance_of(Hash)
expect(refs[project.full_path]).to eq(Set.new(%w[1 2]))
end
end
- describe '#projects_per_reference' do
- it 'returns a Hash containing projects grouped per project paths' do
+ describe '#parent_per_reference' do
+ it 'returns a Hash containing projects grouped per parent paths' do
doc = Nokogiri::HTML.fragment('')
filter = described_class.new(doc, project: project)
- expect(filter).to receive(:references_per_project)
+ expect(filter).to receive(:references_per_parent)
.and_return({ project.full_path => Set.new(%w[1]) })
- expect(filter.projects_per_reference)
+ expect(filter.parent_per_reference)
.to eq({ project.full_path => project })
end
end
- describe '#find_projects_for_paths' do
+ describe '#find_for_paths' do
let(:doc) { Nokogiri::HTML.fragment('') }
let(:filter) { described_class.new(doc, project: project) }
context 'with RequestStore disabled' do
it 'returns a list of Projects for a list of paths' do
- expect(filter.find_projects_for_paths([project.full_path]))
+ expect(filter.find_for_paths([project.full_path]))
.to eq([project])
end
it "return an empty array for paths that don't exist" do
- expect(filter.find_projects_for_paths(['nonexistent/project']))
+ expect(filter.find_for_paths(['nonexistent/project']))
.to eq([])
end
end
context 'with RequestStore enabled', :request_store do
it 'returns a list of Projects for a list of paths' do
- expect(filter.find_projects_for_paths([project.full_path]))
+ expect(filter.find_for_paths([project.full_path]))
.to eq([project])
end
context "when no project with that path exists" do
it "returns no value" do
- expect(filter.find_projects_for_paths(['nonexistent/project']))
+ expect(filter.find_for_paths(['nonexistent/project']))
.to eq([])
end
it "adds the ref to the project refs cache" do
project_refs_cache = {}
- allow(filter).to receive(:project_refs_cache).and_return(project_refs_cache)
+ allow(filter).to receive(:refs_cache).and_return(project_refs_cache)
- filter.find_projects_for_paths(['nonexistent/project'])
+ filter.find_for_paths(['nonexistent/project'])
expect(project_refs_cache).to eq({ 'nonexistent/project' => nil })
end
@@ -71,11 +71,11 @@ describe Banzai::Filter::AbstractReferenceFilter do
context 'when the project refs cache includes nil values' do
before do
# adds { 'nonexistent/project' => nil } to cache
- filter.project_from_ref_cached('nonexistent/project')
+ filter.from_ref_cached('nonexistent/project')
end
it "return an empty array for paths that don't exist" do
- expect(filter.find_projects_for_paths(['nonexistent/project']))
+ expect(filter.find_for_paths(['nonexistent/project']))
.to eq([])
end
end
@@ -83,12 +83,12 @@ describe Banzai::Filter::AbstractReferenceFilter do
end
end
- describe '#current_project_path' do
- it 'returns the path of the current project' do
+ describe '#current_parent_path' do
+ it 'returns the path of the current parent' do
doc = Nokogiri::HTML.fragment('')
filter = described_class.new(doc, project: project)
- expect(filter.current_project_path).to eq(project.full_path)
+ expect(filter.current_parent_path).to eq(project.full_path)
end
end
end
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 702fcac0c6f..080a5f57da9 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -92,6 +92,18 @@ describe Banzai::Filter::CommitReferenceFilter do
expect(link).not_to match %r(https?://)
expect(link).to eq urls.project_commit_url(project, reference, only_path: true)
end
+
+ context "in merge request context" do
+ let(:noteable) { create(:merge_request, target_project: project, source_project: project) }
+ let(:commit) { noteable.commits.first }
+
+ it 'handles merge request contextual commit references' do
+ url = urls.diffs_project_merge_request_url(project, noteable, commit_id: commit.id)
+ doc = reference_filter("See #{reference}", noteable: noteable)
+
+ expect(doc.css('a').first[:href]).to eq(url)
+ end
+ end
end
context 'cross-project / cross-namespace complete reference' do
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index f70c69ef588..3a5f52ea23f 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -157,6 +157,12 @@ describe Banzai::Filter::IssueReferenceFilter do
expect(doc.text).to eq("Fixed (#{project2.full_path}##{issue.iid}.)")
end
+ it 'includes default classes' do
+ doc = reference_filter("Fixed (#{reference}.)")
+
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
+ end
+
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}"
@@ -201,6 +207,12 @@ describe Banzai::Filter::IssueReferenceFilter do
expect(doc.text).to eq("Fixed (#{project2.path}##{issue.iid}.)")
end
+ it 'includes default classes' do
+ doc = reference_filter("Fixed (#{reference}.)")
+
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
+ end
+
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}"
@@ -245,6 +257,12 @@ describe Banzai::Filter::IssueReferenceFilter do
expect(doc.text).to eq("Fixed (#{project2.path}##{issue.iid}.)")
end
+ it 'includes default classes' do
+ doc = reference_filter("Fixed (#{reference}.)")
+
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
+ end
+
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}"
@@ -269,8 +287,15 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
+
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/)
end
+
+ it 'includes default classes' do
+ doc = reference_filter("Fixed (#{reference}.)")
+
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
+ end
end
context 'cross-project reference in link href' do
@@ -291,8 +316,15 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference_link}.)")
+
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
+
+ it 'includes default classes' do
+ doc = reference_filter("Fixed (#{reference_link}.)")
+
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
+ end
end
context 'cross-project URL in link href' do
@@ -313,8 +345,15 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference_link}.)")
+
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
+
+ it 'includes default classes' do
+ doc = reference_filter("Fixed (#{reference_link}.)")
+
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
+ end
end
context 'group context' do
@@ -387,19 +426,19 @@ describe Banzai::Filter::IssueReferenceFilter do
end
end
- describe '#issues_per_project' do
+ describe '#records_per_parent' do
context 'using an internal issue tracker' do
it 'returns a Hash containing the issues per project' do
doc = Nokogiri::HTML.fragment('')
filter = described_class.new(doc, project: project)
- expect(filter).to receive(:projects_per_reference)
+ expect(filter).to receive(:parent_per_reference)
.and_return({ project.full_path => project })
- expect(filter).to receive(:references_per_project)
+ expect(filter).to receive(:references_per_parent)
.and_return({ project.full_path => Set.new([issue.iid]) })
- expect(filter.issues_per_project)
+ expect(filter.records_per_parent)
.to eq({ project => { issue.iid => issue } })
end
end
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 60a88e903ef..76bc0c36ab7 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -89,7 +89,35 @@ describe Banzai::Filter::UploadLinkFilter do
end
end
- context 'when project context does not exist' do
+ context 'in group context' do
+ let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
+ let(:group) { create(:group) }
+ let(:filter_context) { { project: nil, group: group } }
+ let(:relative_path) { "groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
+
+ it 'rewrites the link correctly' do
+ doc = raw_filter(upload_link, filter_context)
+
+ expect(doc.at_css('a')['href']).to eq("#{Gitlab.config.gitlab.url}/#{relative_path}")
+ end
+
+ it 'rewrites the link correctly for subgroup' do
+ subgroup = create(:group, parent: group)
+ relative_path = "groups/#{subgroup.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+
+ doc = raw_filter(upload_link, { project: nil, group: subgroup })
+
+ expect(doc.at_css('a')['href']).to eq("#{Gitlab.config.gitlab.url}/#{relative_path}")
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'), filter_context)
+
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+ end
+
+ context 'when project or group context does not exist' do
let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
it 'does not raise error' do
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 23dbe2b6238..4cef3bdb24b 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -70,12 +70,12 @@ describe Banzai::ReferenceParser::IssueParser do
end
end
- describe '#issues_for_nodes' do
+ describe '#records_for_nodes' do
it 'returns a Hash containing the issues for a list of nodes' do
link['data-issue'] = issue.id.to_s
nodes = [link]
- expect(subject.issues_for_nodes(nodes)).to eq({ link => issue })
+ expect(subject.records_for_nodes(nodes)).to eq({ link => issue })
end
end
end
diff --git a/spec/lib/gitlab/backup/repository_spec.rb b/spec/lib/gitlab/backup/repository_spec.rb
deleted file mode 100644
index 535cce12780..00000000000
--- a/spec/lib/gitlab/backup/repository_spec.rb
+++ /dev/null
@@ -1,117 +0,0 @@
-require 'spec_helper'
-
-describe Backup::Repository do
- let(:progress) { StringIO.new }
- let!(:project) { create(:project) }
-
- before do
- allow(progress).to receive(:puts)
- allow(progress).to receive(:print)
-
- allow_any_instance_of(String).to receive(:color) do |string, _color|
- string
- end
-
- allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
- end
-
- describe '#dump' do
- describe 'repo failure' do
- before do
- allow_any_instance_of(Repository).to receive(:empty_repo?).and_raise(Rugged::OdbError)
- allow(Gitlab::Popen).to receive(:popen).and_return(['normal output', 0])
- end
-
- it 'does not raise error' do
- expect { described_class.new.dump }.not_to raise_error
- end
-
- it 'shows the appropriate error' do
- described_class.new.dump
-
- expect(progress).to have_received(:puts).with("Ignoring repository error and continuing backing up project: #{project.full_path} - Rugged::OdbError")
- end
- end
-
- describe 'command failure' do
- before do
- allow_any_instance_of(Repository).to receive(:empty_repo?).and_return(false)
- allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
- end
-
- it 'shows the appropriate error' do
- described_class.new.dump
-
- expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
- end
- end
- end
-
- describe '#restore' do
- describe 'command failure' do
- before do
- allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
- end
-
- it 'shows the appropriate error' do
- described_class.new.restore
-
- expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
- end
- end
- end
-
- describe '#empty_repo?' do
- context 'for a wiki' do
- let(:wiki) { create(:project_wiki) }
-
- context 'wiki repo has content' do
- let!(:wiki_page) { create(:wiki_page, wiki: wiki) }
-
- before do
- wiki.repository.exists? # initial cache
- end
-
- context '`repository.exists?` is incorrectly cached as false' do
- before do
- repo = wiki.repository
- repo.send(:cache).expire(:exists?)
- repo.send(:cache).fetch(:exists?) { false }
- repo.send(:instance_variable_set, :@exists, false)
- end
-
- it 'returns false, regardless of bad cache value' do
- expect(described_class.new.send(:empty_repo?, wiki)).to be_falsey
- end
- end
-
- context '`repository.exists?` is correctly cached as true' do
- it 'returns false' do
- expect(described_class.new.send(:empty_repo?, wiki)).to be_falsey
- end
- end
- end
-
- context 'wiki repo does not have content' do
- context '`repository.exists?` is incorrectly cached as true' do
- before do
- repo = wiki.repository
- repo.send(:cache).expire(:exists?)
- repo.send(:cache).fetch(:exists?) { true }
- repo.send(:instance_variable_set, :@exists, true)
- end
-
- it 'returns true, regardless of bad cache value' do
- expect(described_class.new.send(:empty_repo?, wiki)).to be_truthy
- end
- end
-
- context '`repository.exists?` is correctly cached as false' do
- it 'returns true' do
- expect(described_class.new.send(:empty_repo?, wiki)).to be_truthy
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 15451c2cf99..0a41362f606 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -31,6 +31,10 @@ describe Gitlab::Diff::InlineDiff do
expect(subject[7]).to eq([17..17])
expect(subject[8]).to be_nil
end
+
+ it 'can handle unchanged empty lines' do
+ expect { described_class.for_lines(['- bar', '+ baz', '']) }.not_to raise_error
+ end
end
describe "#inline_diffs" do
diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
index e361d1a7393..51ce3116880 100644
--- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
@@ -10,14 +10,14 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
stub_config_setting(host: 'localhost')
end
+ after do
+ TestEnv.clean_test_path
+ end
+
let(:email_raw) { fixture_file('emails/valid_new_merge_request.eml') }
let(:namespace) { create(:namespace, path: 'gitlabhq') }
- # project's git repository is not deleted when project is deleted
- # between tests. Then tests fail because re-creation of the project with
- # the same name fails on existing git repository -> skip_disk_validation
- # ignores repository existence on disk
- let!(:project) { create(:project, :public, :repository, skip_disk_validation: true, namespace: namespace, path: 'gitlabhq') }
+ let!(:project) { create(:project, :public, :repository, namespace: namespace, path: 'gitlabhq') }
let!(:user) do
create(
:user,
diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb
index 0506210887c..eb148cc3804 100644
--- a/spec/lib/gitlab/git/remote_repository_spec.rb
+++ b/spec/lib/gitlab/git/remote_repository_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Git::RemoteRepository, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
subject { described_class.new(repository) }
- describe '#empty_repo?' do
+ describe '#empty?' do
using RSpec::Parameterized::TableSyntax
where(:repository, :result) do
@@ -13,7 +13,7 @@ describe Gitlab::Git::RemoteRepository, seed_helper: true do
end
with_them do
- it { expect(subject.empty_repo?).to eq(result) }
+ it { expect(subject.empty?).to eq(result) }
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 08dd6ea80ff..f19b65a5f71 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -257,7 +257,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#empty?' do
- it { expect(repository.empty?).to be_falsey }
+ it { expect(repository).not_to be_empty }
end
describe '#ref_names' do
diff --git a/spec/lib/gitlab/git/storage/checker_spec.rb b/spec/lib/gitlab/git/storage/checker_spec.rb
new file mode 100644
index 00000000000..d74c3bcb04c
--- /dev/null
+++ b/spec/lib/gitlab/git/storage/checker_spec.rb
@@ -0,0 +1,132 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Storage::Checker, :clean_gitlab_redis_shared_state do
+ let(:storage_name) { 'default' }
+ let(:hostname) { Gitlab::Environment.hostname }
+ let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }
+
+ subject(:checker) { described_class.new(storage_name) }
+
+ def value_from_redis(name)
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.hmget(cache_key, name)
+ end.first
+ end
+
+ def set_in_redis(name, value)
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.hmset(cache_key, name, value)
+ end.first
+ end
+
+ describe '.check_all' do
+ it 'calls a check for each storage' do
+ fake_checker_default = double
+ fake_checker_broken = double
+ fake_logger = fake_logger
+
+ expect(described_class).to receive(:new).with('default', fake_logger) { fake_checker_default }
+ expect(described_class).to receive(:new).with('broken', fake_logger) { fake_checker_broken }
+ expect(fake_checker_default).to receive(:check_with_lease)
+ expect(fake_checker_broken).to receive(:check_with_lease)
+
+ described_class.check_all(fake_logger)
+ end
+
+ context 'with broken storage', :broken_storage do
+ it 'returns the results' do
+ expected_result = [
+ { storage: 'default', success: true },
+ { storage: 'broken', success: false }
+ ]
+
+ expect(described_class.check_all).to eq(expected_result)
+ end
+ end
+ end
+
+ describe '#initialize' do
+ it 'assigns the settings' do
+ expect(checker.hostname).to eq(hostname)
+ expect(checker.storage).to eq('default')
+ expect(checker.storage_path).to eq(TestEnv.repos_path)
+ end
+ end
+
+ describe '#check_with_lease' do
+ it 'only allows one check at a time' do
+ expect(checker).to receive(:check).once { sleep 1 }
+
+ thread = Thread.new { checker.check_with_lease }
+ checker.check_with_lease
+ thread.join
+ end
+
+ it 'returns a result hash' do
+ expect(checker.check_with_lease).to eq(storage: 'default', success: true)
+ end
+ end
+
+ describe '#check' do
+ it 'tracks that the storage was accessible' do
+ set_in_redis(:failure_count, 10)
+ set_in_redis(:last_failure, Time.now.to_f)
+
+ checker.check
+
+ expect(value_from_redis(:failure_count).to_i).to eq(0)
+ expect(value_from_redis(:last_failure)).to be_empty
+ expect(value_from_redis(:first_failure)).to be_empty
+ end
+
+ it 'calls the check with the correct arguments' do
+ stub_application_setting(circuitbreaker_storage_timeout: 30,
+ circuitbreaker_access_retries: 3)
+
+ expect(Gitlab::Git::Storage::ForkedStorageCheck)
+ .to receive(:storage_available?).with(TestEnv.repos_path, 30, 3)
+ .and_call_original
+
+ checker.check
+ end
+
+ it 'returns `true`' do
+ expect(checker.check).to eq(true)
+ end
+
+ it 'maintains known storage keys' do
+ Timecop.freeze do
+ # Insert an old key to expire
+ old_entry = Time.now.to_i - 3.days.to_i
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, old_entry, 'to_be_removed')
+ end
+
+ checker.check
+
+ known_keys = Gitlab::Git::Storage.redis.with do |redis|
+ redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
+ end
+
+ expect(known_keys).to contain_exactly(cache_key)
+ end
+ end
+
+ context 'the storage is not available', :broken_storage do
+ let(:storage_name) { 'broken' }
+
+ it 'tracks that the storage was inaccessible' do
+ Timecop.freeze do
+ expect { checker.check }.to change { value_from_redis(:failure_count).to_i }.by(1)
+
+ expect(value_from_redis(:last_failure)).not_to be_empty
+ expect(value_from_redis(:first_failure)).not_to be_empty
+ end
+ end
+
+ it 'returns `false`' do
+ expect(checker.check).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
index f34c9f09057..210b90bfba9 100644
--- a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
+++ b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
@@ -1,11 +1,18 @@
require 'spec_helper'
-describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: true, broken_storage: true do
+describe Gitlab::Git::Storage::CircuitBreaker, :broken_storage do
let(:storage_name) { 'default' }
let(:circuit_breaker) { described_class.new(storage_name, hostname) }
let(:hostname) { Gitlab::Environment.hostname }
let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }
+ def set_in_redis(name, value)
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
+ redis.hmset(cache_key, name, value)
+ end.first
+ end
+
before do
# Override test-settings for the circuitbreaker with something more realistic
# for these specs.
@@ -19,36 +26,7 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
)
end
- def value_from_redis(name)
- Gitlab::Git::Storage.redis.with do |redis|
- redis.hmget(cache_key, name)
- end.first
- end
-
- def set_in_redis(name, value)
- Gitlab::Git::Storage.redis.with do |redis|
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
- redis.hmset(cache_key, name, value)
- end.first
- end
-
- describe '.reset_all!' do
- it 'clears all entries form redis' do
- set_in_redis(:failure_count, 10)
-
- described_class.reset_all!
-
- key_exists = Gitlab::Git::Storage.redis.with { |redis| redis.exists(cache_key) }
-
- expect(key_exists).to be_falsey
- end
-
- it 'does not break when there are no keys in redis' do
- expect { described_class.reset_all! }.not_to raise_error
- end
- end
-
- describe '.for_storage' do
+ describe '.for_storage', :request_store do
it 'only builds a single circuitbreaker per storage' do
expect(described_class).to receive(:new).once.and_call_original
@@ -71,7 +49,6 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
it 'assigns the settings' do
expect(circuit_breaker.hostname).to eq(hostname)
expect(circuit_breaker.storage).to eq('default')
- expect(circuit_breaker.storage_path).to eq(TestEnv.repos_path)
end
end
@@ -91,9 +68,9 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
end
end
- describe '#failure_wait_time' do
+ describe '#check_interval' do
it 'reads the value from settings' do
- expect(circuit_breaker.failure_wait_time).to eq(1)
+ expect(circuit_breaker.check_interval).to eq(1)
end
end
@@ -114,12 +91,6 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
expect(circuit_breaker.access_retries).to eq(4)
end
end
-
- describe '#backoff_threshold' do
- it 'reads the value from settings' do
- expect(circuit_breaker.backoff_threshold).to eq(5)
- end
- end
end
describe '#perform' do
@@ -134,19 +105,6 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
end
end
- it 'raises the correct exception when backing off' do
- Timecop.freeze do
- set_in_redis(:last_failure, 1.second.ago.to_f)
- set_in_redis(:failure_count, 90)
-
- expect { |b| circuit_breaker.perform(&b) }
- .to raise_error do |exception|
- expect(exception).to be_kind_of(Gitlab::Git::Storage::Failing)
- expect(exception.retry_after).to eq(30)
- end
- end
- end
-
it 'yields the block' do
expect { |b| circuit_breaker.perform(&b) }
.to yield_control
@@ -170,54 +128,6 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
.to raise_error(Rugged::OSError)
end
- it 'tracks that the storage was accessible' do
- set_in_redis(:failure_count, 10)
- set_in_redis(:last_failure, Time.now.to_f)
-
- circuit_breaker.perform { '' }
-
- expect(value_from_redis(:failure_count).to_i).to eq(0)
- expect(value_from_redis(:last_failure)).to be_empty
- expect(circuit_breaker.failure_count).to eq(0)
- expect(circuit_breaker.last_failure).to be_nil
- end
-
- it 'maintains known storage keys' do
- Timecop.freeze do
- # Insert an old key to expire
- old_entry = Time.now.to_i - 3.days.to_i
- Gitlab::Git::Storage.redis.with do |redis|
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, old_entry, 'to_be_removed')
- end
-
- circuit_breaker.perform { '' }
-
- known_keys = Gitlab::Git::Storage.redis.with do |redis|
- redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
- end
-
- expect(known_keys).to contain_exactly(cache_key)
- end
- end
-
- it 'only performs the accessibility check once' do
- expect(Gitlab::Git::Storage::ForkedStorageCheck)
- .to receive(:storage_available?).once.and_call_original
-
- 2.times { circuit_breaker.perform { '' } }
- end
-
- it 'calls the check with the correct arguments' do
- stub_application_setting(circuitbreaker_storage_timeout: 30,
- circuitbreaker_access_retries: 3)
-
- expect(Gitlab::Git::Storage::ForkedStorageCheck)
- .to receive(:storage_available?).with(TestEnv.repos_path, 30, 3)
- .and_call_original
-
- circuit_breaker.perform { '' }
- end
-
context 'with the feature disabled' do
before do
stub_feature_flags(git_storage_circuit_breaker: false)
@@ -240,31 +150,6 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
expect(result).to eq('hello')
end
end
-
- context 'the storage is not available' do
- let(:storage_name) { 'broken' }
-
- it 'raises the correct exception' do
- expect(circuit_breaker).to receive(:track_storage_inaccessible)
-
- expect { circuit_breaker.perform { '' } }
- .to raise_error do |exception|
- expect(exception).to be_kind_of(Gitlab::Git::Storage::Inaccessible)
- expect(exception.retry_after).to eq(30)
- end
- end
-
- it 'tracks that the storage was inaccessible' do
- Timecop.freeze do
- expect { circuit_breaker.perform { '' } }.to raise_error(Gitlab::Git::Storage::Inaccessible)
-
- expect(value_from_redis(:failure_count).to_i).to eq(1)
- expect(value_from_redis(:last_failure)).not_to be_empty
- expect(circuit_breaker.failure_count).to eq(1)
- expect(circuit_breaker.last_failure).to be_within(1.second).of(Time.now)
- end
- end
- end
end
describe '#circuit_broken?' do
@@ -283,32 +168,6 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
end
end
- describe '#backing_off?' do
- it 'is true when there was a recent failure' do
- Timecop.freeze do
- set_in_redis(:last_failure, 1.second.ago.to_f)
- set_in_redis(:failure_count, 90)
-
- expect(circuit_breaker.backing_off?).to be_truthy
- end
- end
-
- context 'the `failure_wait_time` is set to 0' do
- before do
- stub_application_setting(circuitbreaker_failure_wait_time: 0)
- end
-
- it 'is working even when there are failures' do
- Timecop.freeze do
- set_in_redis(:last_failure, 0.seconds.ago.to_f)
- set_in_redis(:failure_count, 90)
-
- expect(circuit_breaker.backing_off?).to be_falsey
- end
- end
- end
- end
-
describe '#last_failure' do
it 'returns the last failure time' do
time = Time.parse("2017-05-26 17:52:30")
diff --git a/spec/lib/gitlab/git/storage/failure_info_spec.rb b/spec/lib/gitlab/git/storage/failure_info_spec.rb
new file mode 100644
index 00000000000..bae88fdda86
--- /dev/null
+++ b/spec/lib/gitlab/git/storage/failure_info_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Storage::FailureInfo, :broken_storage do
+ let(:storage_name) { 'default' }
+ let(:hostname) { Gitlab::Environment.hostname }
+ let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }
+
+ def value_from_redis(name)
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.hmget(cache_key, name)
+ end.first
+ end
+
+ def set_in_redis(name, value)
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
+ redis.hmset(cache_key, name, value)
+ end.first
+ end
+
+ describe '.reset_all!' do
+ it 'clears all entries form redis' do
+ set_in_redis(:failure_count, 10)
+
+ described_class.reset_all!
+
+ key_exists = Gitlab::Git::Storage.redis.with { |redis| redis.exists(cache_key) }
+
+ expect(key_exists).to be_falsey
+ end
+
+ it 'does not break when there are no keys in redis' do
+ expect { described_class.reset_all! }.not_to raise_error
+ end
+ end
+
+ describe '.load' do
+ it 'loads failure information for a storage on a host' do
+ first_failure = Time.parse("2017-11-14 17:52:30")
+ last_failure = Time.parse("2017-11-14 18:54:37")
+ failure_count = 11
+
+ set_in_redis(:first_failure, first_failure.to_i)
+ set_in_redis(:last_failure, last_failure.to_i)
+ set_in_redis(:failure_count, failure_count.to_i)
+
+ info = described_class.load(cache_key)
+
+ expect(info.first_failure).to eq(first_failure)
+ expect(info.last_failure).to eq(last_failure)
+ expect(info.failure_count).to eq(failure_count)
+ end
+ end
+
+ describe '#no_failures?' do
+ it 'is true when there are no failures' do
+ info = described_class.new(nil, nil, 0)
+
+ expect(info.no_failures?).to be_truthy
+ end
+
+ it 'is false when there are failures' do
+ info = described_class.new(Time.parse("2017-11-14 17:52:30"),
+ Time.parse("2017-11-14 18:54:37"),
+ 20)
+
+ expect(info.no_failures?).to be_falsy
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/storage/health_spec.rb b/spec/lib/gitlab/git/storage/health_spec.rb
index d7a52a04fbb..bb670fc5d94 100644
--- a/spec/lib/gitlab/git/storage/health_spec.rb
+++ b/spec/lib/gitlab/git/storage/health_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Git::Storage::Health, clean_gitlab_redis_shared_state: true, broken_storage: true do
+describe Gitlab::Git::Storage::Health, broken_storage: true do
let(:host1_key) { 'storage_accessible:broken:web01' }
let(:host2_key) { 'storage_accessible:default:kiq01' }
diff --git a/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb
index 5db37f55e03..93ad20011de 100644
--- a/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb
+++ b/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb
@@ -27,7 +27,7 @@ describe Gitlab::Git::Storage::NullCircuitBreaker do
end
describe '#failure_info' do
- it { Timecop.freeze { expect(breaker.failure_info).to eq(Gitlab::Git::Storage::CircuitBreaker::FailureInfo.new(Time.now, breaker.failure_count_threshold)) } }
+ it { expect(breaker.failure_info.no_failures?).to be_falsy }
end
end
@@ -49,7 +49,7 @@ describe Gitlab::Git::Storage::NullCircuitBreaker do
end
describe '#failure_info' do
- it { expect(breaker.failure_info).to eq(Gitlab::Git::Storage::CircuitBreaker::FailureInfo.new(nil, 0)) }
+ it { expect(breaker.failure_info.no_failures?).to be_truthy }
end
end
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index 494dfe0e595..ce15057dd7d 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -38,4 +38,29 @@ describe Gitlab::Git do
expect(described_class.ref_name(utf8_invalid_ref)).to eq("an_invalid_ref_Ã¥")
end
end
+
+ describe '.shas_eql?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:sha1, :sha2, :result) do
+ sha = RepoHelpers.sample_commit.id
+ short_sha = sha[0, Gitlab::Git::Commit::MIN_SHA_LENGTH]
+ too_short_sha = sha[0, Gitlab::Git::Commit::MIN_SHA_LENGTH - 1]
+
+ [
+ [sha, sha, true],
+ [sha, short_sha, true],
+ [sha, sha.reverse, false],
+ [sha, too_short_sha, false],
+ [sha, nil, false]
+ ]
+ end
+
+ with_them do
+ it { expect(described_class.shas_eql?(sha1, sha2)).to eq(result) }
+ it 'is commutative' do
+ expect(described_class.shas_eql?(sha2, sha1)).to eq(result)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 476a3f1998d..ef874368077 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -250,4 +250,34 @@ describe Gitlab::ReferenceExtractor do
subject { described_class.references_pattern }
it { is_expected.to be_kind_of Regexp }
end
+
+ describe 'referables prefixes' do
+ def prefixes
+ described_class::REFERABLES.each_with_object({}) do |referable, result|
+ klass = referable.to_s.camelize.constantize
+
+ next unless klass.respond_to?(:reference_prefix)
+
+ prefix = klass.reference_prefix
+ result[prefix] ||= []
+ result[prefix] << referable
+ end
+ end
+
+ it 'returns all supported prefixes' do
+ expect(prefixes.keys.uniq).to match_array(%w(@ # ~ % ! $ &))
+ end
+
+ it 'does not allow one prefix for multiple referables if not allowed specificly' do
+ # make sure you are not overriding existing prefix before changing this hash
+ multiple_allowed = {
+ '@' => 3
+ }
+
+ prefixes.each do |prefix, referables|
+ expected_count = multiple_allowed[prefix] || 1
+ expect(referables.count).to eq(expected_count)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/storage_check/cli_spec.rb b/spec/lib/gitlab/storage_check/cli_spec.rb
new file mode 100644
index 00000000000..6db0925899c
--- /dev/null
+++ b/spec/lib/gitlab/storage_check/cli_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Gitlab::StorageCheck::CLI do
+ let(:options) { Gitlab::StorageCheck::Options.new('unix://tmp/socket.sock', nil, 1, false) }
+ subject(:runner) { described_class.new(options) }
+
+ describe '#update_settings' do
+ it 'updates the interval when changed in a valid response and logs the change' do
+ fake_response = double
+ expect(fake_response).to receive(:valid?).and_return(true)
+ expect(fake_response).to receive(:check_interval).and_return(42)
+ expect(runner.logger).to receive(:info)
+
+ runner.update_settings(fake_response)
+
+ expect(options.interval).to eq(42)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/storage_check/gitlab_caller_spec.rb b/spec/lib/gitlab/storage_check/gitlab_caller_spec.rb
new file mode 100644
index 00000000000..d869022fd31
--- /dev/null
+++ b/spec/lib/gitlab/storage_check/gitlab_caller_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Gitlab::StorageCheck::GitlabCaller do
+ let(:options) { Gitlab::StorageCheck::Options.new('unix://tmp/socket.sock', nil, nil, false) }
+ subject(:gitlab_caller) { described_class.new(options) }
+
+ describe '#call!' do
+ context 'when a socket is given' do
+ it 'calls a socket' do
+ fake_connection = double
+ expect(fake_connection).to receive(:post)
+ expect(Excon).to receive(:new).with('unix://tmp/socket.sock', socket: "tmp/socket.sock") { fake_connection }
+
+ gitlab_caller.call!
+ end
+ end
+
+ context 'when a host is given' do
+ let(:options) { Gitlab::StorageCheck::Options.new('http://localhost:8080', nil, nil, false) }
+
+ it 'it calls a http response' do
+ fake_connection = double
+ expect(Excon).to receive(:new).with('http://localhost:8080', socket: nil) { fake_connection }
+ expect(fake_connection).to receive(:post)
+
+ gitlab_caller.call!
+ end
+ end
+ end
+
+ describe '#headers' do
+ it 'Adds the JSON header' do
+ headers = gitlab_caller.headers
+
+ expect(headers['Content-Type']).to eq('application/json')
+ end
+
+ context 'when a token was provided' do
+ let(:options) { Gitlab::StorageCheck::Options.new('unix://tmp/socket.sock', 'atoken', nil, false) }
+
+ it 'adds it to the headers' do
+ expect(gitlab_caller.headers['TOKEN']).to eq('atoken')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/storage_check/option_parser_spec.rb b/spec/lib/gitlab/storage_check/option_parser_spec.rb
new file mode 100644
index 00000000000..cad4dfbefcf
--- /dev/null
+++ b/spec/lib/gitlab/storage_check/option_parser_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Gitlab::StorageCheck::OptionParser do
+ describe '.parse!' do
+ it 'assigns all options' do
+ args = %w(--target unix://tmp/hello/world.sock --token thetoken --interval 42)
+
+ options = described_class.parse!(args)
+
+ expect(options.token).to eq('thetoken')
+ expect(options.interval).to eq(42)
+ expect(options.target).to eq('unix://tmp/hello/world.sock')
+ end
+
+ it 'requires the interval to be a number' do
+ args = %w(--target unix://tmp/hello/world.sock --interval fortytwo)
+
+ expect { described_class.parse!(args) }.to raise_error(OptionParser::InvalidArgument)
+ end
+
+ it 'raises an error if the scheme is not included' do
+ args = %w(--target tmp/hello/world.sock)
+
+ expect { described_class.parse!(args) }.to raise_error(OptionParser::InvalidArgument)
+ end
+
+ it 'raises an error if both socket and host are missing' do
+ expect { described_class.parse!([]) }.to raise_error(OptionParser::InvalidArgument)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/storage_check/response_spec.rb b/spec/lib/gitlab/storage_check/response_spec.rb
new file mode 100644
index 00000000000..0ff2963e443
--- /dev/null
+++ b/spec/lib/gitlab/storage_check/response_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::StorageCheck::Response do
+ let(:fake_json) do
+ {
+ check_interval: 42,
+ results: [
+ { storage: 'working', success: true },
+ { storage: 'skipped', success: nil },
+ { storage: 'failing', success: false }
+ ]
+ }.to_json
+ end
+
+ let(:fake_http_response) do
+ fake_response = instance_double("Excon::Response - Status check")
+ allow(fake_response).to receive(:status).and_return(200)
+ allow(fake_response).to receive(:body).and_return(fake_json)
+ allow(fake_response).to receive(:headers).and_return('Content-Type' => 'application/json')
+
+ fake_response
+ end
+ let(:response) { described_class.new(fake_http_response) }
+
+ describe '#valid?' do
+ it 'is valid for a success response with parseable JSON' do
+ expect(response).to be_valid
+ end
+ end
+
+ describe '#check_interval' do
+ it 'returns the result from the JSON' do
+ expect(response.check_interval).to eq(42)
+ end
+ end
+
+ describe '#responsive_shards' do
+ it 'contains the names of working shards' do
+ expect(response.responsive_shards).to contain_exactly('working')
+ end
+ end
+
+ describe '#skipped_shards' do
+ it 'contains the names of skipped shards' do
+ expect(response.skipped_shards).to contain_exactly('skipped')
+ end
+ end
+
+ describe '#failing_shards' do
+ it 'contains the name of failing shards' do
+ expect(response.failing_shards).to contain_exactly('failing')
+ end
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 0b7e16cc33c..ef480e7a80a 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -115,9 +115,8 @@ describe ApplicationSetting do
end
context 'circuitbreaker settings' do
- [:circuitbreaker_backoff_threshold,
- :circuitbreaker_failure_count_threshold,
- :circuitbreaker_failure_wait_time,
+ [:circuitbreaker_failure_count_threshold,
+ :circuitbreaker_check_interval,
:circuitbreaker_failure_reset_time,
:circuitbreaker_storage_timeout].each do |field|
it "Validates #{field} as number" do
@@ -126,16 +125,6 @@ describe ApplicationSetting do
.is_greater_than_or_equal_to(0)
end
end
-
- it 'requires the `backoff_threshold` to be lower than the `failure_count_threshold`' do
- setting.circuitbreaker_failure_count_threshold = 10
- setting.circuitbreaker_backoff_threshold = 15
- failure_message = "The circuitbreaker backoff threshold should be lower "\
- "than the failure count threshold"
-
- expect(setting).not_to be_valid
- expect(setting.errors[:circuitbreaker_backoff_threshold]).to include(failure_message)
- end
end
context 'repository storages' do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 26d33663dad..a6258676767 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1868,6 +1868,94 @@ describe Ci::Build do
end
end
+ describe 'state transition: any => [:running]' do
+ shared_examples 'validation is active' do
+ context 'when depended job has not been completed yet' do
+ let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ it { expect { job.run! }.to raise_error(Ci::Build::MissingDependenciesError) }
+ end
+
+ context 'when artifacts of depended job has been expired' do
+ let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ it { expect { job.run! }.to raise_error(Ci::Build::MissingDependenciesError) }
+ end
+
+ context 'when artifacts of depended job has been erased' do
+ let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
+
+ before do
+ pre_stage_job.erase
+ end
+
+ it { expect { job.run! }.to raise_error(Ci::Build::MissingDependenciesError) }
+ end
+ end
+
+ shared_examples 'validation is not active' do
+ context 'when depended job has not been completed yet' do
+ let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ it { expect { job.run! }.not_to raise_error }
+ end
+
+ context 'when artifacts of depended job has been expired' do
+ let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ it { expect { job.run! }.not_to raise_error }
+ end
+
+ context 'when artifacts of depended job has been erased' do
+ let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
+
+ before do
+ pre_stage_job.erase
+ end
+
+ it { expect { job.run! }.not_to raise_error }
+ end
+ end
+
+ let!(:job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: options) }
+
+ context 'when validates for dependencies is enabled' do
+ before do
+ stub_feature_flags(ci_disable_validates_dependencies: false)
+ end
+
+ let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ context 'when "dependencies" keyword is not defined' do
+ let(:options) { {} }
+
+ it { expect { job.run! }.not_to raise_error }
+ end
+
+ context 'when "dependencies" keyword is empty' do
+ let(:options) { { dependencies: [] } }
+
+ it { expect { job.run! }.not_to raise_error }
+ end
+
+ context 'when "dependencies" keyword is specified' do
+ let(:options) { { dependencies: ['test'] } }
+
+ it_behaves_like 'validation is active'
+ end
+ end
+
+ context 'when validates for dependencies is disabled' do
+ let(:options) { { dependencies: ['test'] } }
+
+ before do
+ stub_feature_flags(ci_disable_validates_dependencies: true)
+ end
+
+ it_behaves_like 'validation is not active'
+ end
+ end
+
describe 'state transition when build fails' do
let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user) }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index d4b1e7c8dd4..bb89e093890 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1244,7 +1244,7 @@ describe Ci::Pipeline, :mailer do
describe '#execute_hooks' do
let!(:build_a) { create_build('a', 0) }
- let!(:build_b) { create_build('b', 1) }
+ let!(:build_b) { create_build('b', 0) }
let!(:hook) do
create(:project_hook, project: project, pipeline_events: enabled)
@@ -1300,6 +1300,8 @@ describe Ci::Pipeline, :mailer do
end
context 'when stage one failed' do
+ let!(:build_b) { create_build('b', 1) }
+
before do
build_a.drop
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 8389d5c5430..4d0b3245a13 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -9,13 +9,14 @@ describe DiffNote do
let(:path) { "files/ruby/popen.rb" }
+ let(:diff_refs) { merge_request.diff_refs }
let!(:position) do
Gitlab::Diff::Position.new(
old_path: path,
new_path: path,
old_line: nil,
new_line: 14,
- diff_refs: merge_request.diff_refs
+ diff_refs: diff_refs
)
end
@@ -25,7 +26,7 @@ describe DiffNote do
new_path: path,
old_line: 16,
new_line: 22,
- diff_refs: merge_request.diff_refs
+ diff_refs: diff_refs
)
end
@@ -158,25 +159,21 @@ describe DiffNote do
describe "creation" do
describe "updating of position" do
context "when noteable is a commit" do
- let(:diff_note) { create(:diff_note_on_commit, project: project, position: position) }
+ let(:diff_refs) { commit.diff_refs }
- it "doesn't update the position" do
- diff_note
+ subject { create(:diff_note_on_commit, project: project, position: position, commit_id: commit.id) }
- expect(diff_note.original_position).to eq(position)
- expect(diff_note.position).to eq(position)
+ it "doesn't update the position" do
+ is_expected.to have_attributes(original_position: position,
+ position: position)
end
end
context "when noteable is a merge request" do
- let(:diff_note) { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
-
context "when the note is active" do
it "doesn't update the position" do
- diff_note
-
- expect(diff_note.original_position).to eq(position)
- expect(diff_note.position).to eq(position)
+ expect(subject.original_position).to eq(position)
+ expect(subject.position).to eq(position)
end
end
@@ -186,10 +183,8 @@ describe DiffNote do
end
it "updates the position" do
- diff_note
-
- expect(diff_note.original_position).to eq(position)
- expect(diff_note.position).not_to eq(position)
+ expect(subject.original_position).to eq(position)
+ expect(subject.position).not_to eq(position)
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 71fbb82184c..30a5a3bbff7 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -967,7 +967,7 @@ describe MergeRequest do
end
shared_examples 'returning all SHA' do
- it 'returns all SHA from all merge_request_diffs' do
+ it 'returns all SHAs from all merge_request_diffs' do
expect(subject.merge_request_diffs.size).to eq(2)
expect(subject.all_commit_shas).to match_array(all_commit_shas)
end
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 01440b15674..2bb1c49b740 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe PersonalAccessToken do
+ subject { described_class }
+
describe '.build' do
let(:personal_access_token) { build(:personal_access_token) }
let(:invalid_personal_access_token) { build(:personal_access_token, :invalid) }
@@ -45,6 +47,29 @@ describe PersonalAccessToken do
end
end
+ describe 'Redis storage' do
+ let(:user_id) { 123 }
+ let(:token) { 'abc000foo' }
+
+ before do
+ subject.redis_store!(user_id, token)
+ end
+
+ it 'returns stored data' do
+ expect(subject.redis_getdel(user_id)).to eq(token)
+ end
+
+ context 'after deletion' do
+ before do
+ expect(subject.redis_getdel(user_id)).to eq(token)
+ end
+
+ it 'token is removed' do
+ expect(subject.redis_getdel(user_id)).to be_nil
+ end
+ end
+ end
+
context "validations" do
let(:personal_access_token) { build(:personal_access_token) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index bda1d1cb612..f4699fd243d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -313,7 +313,6 @@ describe Project do
it { is_expected.to delegate_method(method).to(:team) }
end
- it { is_expected.to delegate_method(:empty_repo?).to(:repository) }
it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
end
@@ -656,6 +655,24 @@ describe Project do
end
end
+ describe '#empty_repo?' do
+ context 'when the repo does not exist' do
+ let(:project) { build_stubbed(:project) }
+
+ it 'returns true' do
+ expect(project.empty_repo?).to be(true)
+ end
+ end
+
+ context 'when the repo exists' do
+ let(:project) { create(:project, :repository) }
+ let(:empty_project) { create(:project, :empty_repo) }
+
+ it { expect(empty_project.empty_repo?).to be(true) }
+ it { expect(project.empty_repo?).to be(false) }
+ end
+ end
+
describe '#external_issue_tracker' do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index d37e3d2c527..358bc3dfb94 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -29,7 +29,9 @@ describe Repository do
def expect_to_raise_storage_error
expect { yield }.to raise_error do |exception|
storage_exceptions = [Gitlab::Git::Storage::Inaccessible, Gitlab::Git::CommandError, GRPC::Unavailable]
- expect(exception.class).to be_in(storage_exceptions)
+ known_exception = storage_exceptions.select { |e| exception.is_a?(e) }
+
+ expect(known_exception).not_to be_nil
end
end
@@ -583,7 +585,7 @@ describe Repository do
end
it 'properly handles query when repo is empty' do
- repository = create(:project).repository
+ repository = create(:project, :empty_repo).repository
results = repository.search_files_by_content('test', 'master')
expect(results).to match_array([])
@@ -619,7 +621,7 @@ describe Repository do
end
it 'properly handles query when repo is empty' do
- repository = create(:project).repository
+ repository = create(:project, :empty_repo).repository
results = repository.search_files_by_name('test', 'master')
@@ -634,9 +636,7 @@ describe Repository do
end
describe '#fetch_ref' do
- # Setting the var here, sidesteps the stub that makes gitaly raise an error
- # before the actual test call
- set(:broken_repository) { create(:project, :broken_storage).repository }
+ let(:broken_repository) { create(:project, :broken_storage).repository }
describe 'when storage is broken', :broken_storage do
it 'should raise a storage error' do
@@ -1204,17 +1204,15 @@ describe Repository do
let(:empty_repository) { create(:project_empty_repo).repository }
it 'returns true for an empty repository' do
- expect(empty_repository.empty?).to eq(true)
+ expect(empty_repository).to be_empty
end
it 'returns false for a non-empty repository' do
- expect(repository.empty?).to eq(false)
+ expect(repository).not_to be_empty
end
it 'caches the output' do
- expect(repository.raw_repository).to receive(:empty?)
- .once
- .and_return(false)
+ expect(repository.raw_repository).to receive(:has_visible_content?).once
repository.empty?
repository.empty?
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b27c1b2cd1a..03c96a8f5aa 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2433,4 +2433,163 @@ describe User do
expect(user).not_to be_blocked
end
end
+
+ describe '#max_member_access_for_project_ids' do
+ shared_examples 'max member access for projects' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:owner_project) { create(:project, group: group) }
+ let(:master_project) { create(:project) }
+ let(:reporter_project) { create(:project) }
+ let(:developer_project) { create(:project) }
+ let(:guest_project) { create(:project) }
+ let(:no_access_project) { create(:project) }
+
+ let(:projects) do
+ [owner_project, master_project, reporter_project, developer_project, guest_project, no_access_project].map(&:id)
+ end
+
+ let(:expected) do
+ {
+ owner_project.id => Gitlab::Access::OWNER,
+ master_project.id => Gitlab::Access::MASTER,
+ reporter_project.id => Gitlab::Access::REPORTER,
+ developer_project.id => Gitlab::Access::DEVELOPER,
+ guest_project.id => Gitlab::Access::GUEST,
+ no_access_project.id => Gitlab::Access::NO_ACCESS
+ }
+ end
+
+ before do
+ create(:group_member, user: user, group: group)
+ master_project.add_master(user)
+ reporter_project.add_reporter(user)
+ developer_project.add_developer(user)
+ guest_project.add_guest(user)
+ end
+
+ it 'returns correct roles for different projects' do
+ expect(user.max_member_access_for_project_ids(projects)).to eq(expected)
+ end
+ end
+
+ context 'with RequestStore enabled', :request_store do
+ include_examples 'max member access for projects'
+
+ def access_levels(projects)
+ user.max_member_access_for_project_ids(projects)
+ end
+
+ it 'does not perform extra queries when asked for projects who have already been found' do
+ access_levels(projects)
+
+ expect { access_levels(projects) }.not_to exceed_query_limit(0)
+
+ expect(access_levels(projects)).to eq(expected)
+ end
+
+ it 'only requests the extra projects when uncached projects are passed' do
+ second_master_project = create(:project)
+ second_developer_project = create(:project)
+ second_master_project.add_master(user)
+ second_developer_project.add_developer(user)
+
+ all_projects = projects + [second_master_project.id, second_developer_project.id]
+
+ expected_all = expected.merge(second_master_project.id => Gitlab::Access::MASTER,
+ second_developer_project.id => Gitlab::Access::DEVELOPER)
+
+ access_levels(projects)
+
+ queries = ActiveRecord::QueryRecorder.new { access_levels(all_projects) }
+
+ expect(queries.count).to eq(1)
+ expect(queries.log_message).to match(/\W(#{second_master_project.id}, #{second_developer_project.id})\W/)
+ expect(access_levels(all_projects)).to eq(expected_all)
+ end
+ end
+
+ context 'with RequestStore disabled' do
+ include_examples 'max member access for projects'
+ end
+ end
+
+ describe '#max_member_access_for_group_ids' do
+ shared_examples 'max member access for groups' do
+ let(:user) { create(:user) }
+ let(:owner_group) { create(:group) }
+ let(:master_group) { create(:group) }
+ let(:reporter_group) { create(:group) }
+ let(:developer_group) { create(:group) }
+ let(:guest_group) { create(:group) }
+ let(:no_access_group) { create(:group) }
+
+ let(:groups) do
+ [owner_group, master_group, reporter_group, developer_group, guest_group, no_access_group].map(&:id)
+ end
+
+ let(:expected) do
+ {
+ owner_group.id => Gitlab::Access::OWNER,
+ master_group.id => Gitlab::Access::MASTER,
+ reporter_group.id => Gitlab::Access::REPORTER,
+ developer_group.id => Gitlab::Access::DEVELOPER,
+ guest_group.id => Gitlab::Access::GUEST,
+ no_access_group.id => Gitlab::Access::NO_ACCESS
+ }
+ end
+
+ before do
+ owner_group.add_owner(user)
+ master_group.add_master(user)
+ reporter_group.add_reporter(user)
+ developer_group.add_developer(user)
+ guest_group.add_guest(user)
+ end
+
+ it 'returns correct roles for different groups' do
+ expect(user.max_member_access_for_group_ids(groups)).to eq(expected)
+ end
+ end
+
+ context 'with RequestStore enabled', :request_store do
+ include_examples 'max member access for groups'
+
+ def access_levels(groups)
+ user.max_member_access_for_group_ids(groups)
+ end
+
+ it 'does not perform extra queries when asked for groups who have already been found' do
+ access_levels(groups)
+
+ expect { access_levels(groups) }.not_to exceed_query_limit(0)
+
+ expect(access_levels(groups)).to eq(expected)
+ end
+
+ it 'only requests the extra groups when uncached groups are passed' do
+ second_master_group = create(:group)
+ second_developer_group = create(:group)
+ second_master_group.add_master(user)
+ second_developer_group.add_developer(user)
+
+ all_groups = groups + [second_master_group.id, second_developer_group.id]
+
+ expected_all = expected.merge(second_master_group.id => Gitlab::Access::MASTER,
+ second_developer_group.id => Gitlab::Access::DEVELOPER)
+
+ access_levels(groups)
+
+ queries = ActiveRecord::QueryRecorder.new { access_levels(all_groups) }
+
+ expect(queries.count).to eq(1)
+ expect(queries.log_message).to match(/\W(#{second_master_group.id}, #{second_developer_group.id})\W/)
+ expect(access_levels(all_groups)).to eq(expected_all)
+ end
+ end
+
+ context 'with RequestStore disabled' do
+ include_examples 'max member access for groups'
+ end
+ end
end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 4f4e634829d..b4d25e06d9a 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -9,6 +9,8 @@ describe GroupPolicy do
let(:admin) { create(:admin) }
let(:group) { create(:group) }
+ let(:guest_permissions) { [:read_group, :upload_file, :read_namespace] }
+
let(:reporter_permissions) { [:admin_label] }
let(:developer_permissions) { [:admin_milestones] }
@@ -52,6 +54,7 @@ describe GroupPolicy do
it do
expect_allowed(:read_group)
+ expect_disallowed(:upload_file)
expect_disallowed(*reporter_permissions)
expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions)
@@ -64,7 +67,7 @@ describe GroupPolicy do
let(:current_user) { guest }
it do
- expect_allowed(:read_group, :read_namespace)
+ expect_allowed(*guest_permissions)
expect_disallowed(*reporter_permissions)
expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions)
@@ -76,7 +79,7 @@ describe GroupPolicy do
let(:current_user) { reporter }
it do
- expect_allowed(:read_group, :read_namespace)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions)
@@ -88,7 +91,7 @@ describe GroupPolicy do
let(:current_user) { developer }
it do
- expect_allowed(:read_group, :read_namespace)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
expect_disallowed(*master_permissions)
@@ -100,7 +103,7 @@ describe GroupPolicy do
let(:current_user) { master }
it do
- expect_allowed(:read_group, :read_namespace)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
expect_allowed(*master_permissions)
@@ -114,7 +117,7 @@ describe GroupPolicy do
it do
allow(Group).to receive(:supports_nested_groups?).and_return(true)
- expect_allowed(:read_group, :read_namespace)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
expect_allowed(*master_permissions)
@@ -128,7 +131,7 @@ describe GroupPolicy do
it do
allow(Group).to receive(:supports_nested_groups?).and_return(true)
- expect_allowed(:read_group, :read_namespace)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
expect_allowed(*master_permissions)
@@ -187,7 +190,7 @@ describe GroupPolicy do
let(:current_user) { nil }
it do
- expect_disallowed(:read_group)
+ expect_disallowed(*guest_permissions)
expect_disallowed(*reporter_permissions)
expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions)
@@ -199,7 +202,7 @@ describe GroupPolicy do
let(:current_user) { guest }
it do
- expect_allowed(:read_group)
+ expect_allowed(*guest_permissions)
expect_disallowed(*reporter_permissions)
expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions)
@@ -211,7 +214,7 @@ describe GroupPolicy do
let(:current_user) { reporter }
it do
- expect_allowed(:read_group)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions)
@@ -223,7 +226,7 @@ describe GroupPolicy do
let(:current_user) { developer }
it do
- expect_allowed(:read_group)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
expect_disallowed(*master_permissions)
@@ -235,7 +238,7 @@ describe GroupPolicy do
let(:current_user) { master }
it do
- expect_allowed(:read_group)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
expect_allowed(*master_permissions)
@@ -249,7 +252,7 @@ describe GroupPolicy do
it do
allow(Group).to receive(:supports_nested_groups?).and_return(true)
- expect_allowed(:read_group)
+ expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
expect_allowed(*master_permissions)
diff --git a/spec/requests/api/circuit_breakers_spec.rb b/spec/requests/api/circuit_breakers_spec.rb
index 3b858c40fd6..fe76f057115 100644
--- a/spec/requests/api/circuit_breakers_spec.rb
+++ b/spec/requests/api/circuit_breakers_spec.rb
@@ -47,7 +47,7 @@ describe API::CircuitBreakers do
describe 'DELETE circuit_breakers/repository_storage' do
it 'clears all circuit_breakers' do
- expect(Gitlab::Git::Storage::CircuitBreaker).to receive(:reset_all!)
+ expect(Gitlab::Git::Storage::FailureInfo).to receive(:reset_all!)
delete api('/circuit_breakers/repository_storage', admin)
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index 07d7f96bd70..10e6a3c07c8 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -95,6 +95,12 @@ describe API::ProtectedBranches do
describe 'POST /projects/:id/protected_branches' do
let(:branch_name) { 'new_branch' }
+ let(:post_endpoint) { api("/projects/#{project.id}/protected_branches", user) }
+
+ def expect_protection_to_be_successful
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['name']).to eq(branch_name)
+ end
context 'when authenticated as a master' do
before do
@@ -102,7 +108,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch' do
- post api("/projects/#{project.id}/protected_branches", user), name: branch_name
+ post post_endpoint, name: branch_name
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -111,8 +117,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and developers can push' do
- post api("/projects/#{project.id}/protected_branches", user),
- name: branch_name, push_access_level: 30
+ post post_endpoint, name: branch_name, push_access_level: 30
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -121,8 +126,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and developers can merge' do
- post api("/projects/#{project.id}/protected_branches", user),
- name: branch_name, merge_access_level: 30
+ post post_endpoint, name: branch_name, merge_access_level: 30
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -131,8 +135,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and developers can push and merge' do
- post api("/projects/#{project.id}/protected_branches", user),
- name: branch_name, push_access_level: 30, merge_access_level: 30
+ post post_endpoint, name: branch_name, push_access_level: 30, merge_access_level: 30
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -141,8 +144,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and no one can push' do
- post api("/projects/#{project.id}/protected_branches", user),
- name: branch_name, push_access_level: 0
+ post post_endpoint, name: branch_name, push_access_level: 0
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -151,8 +153,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and no one can merge' do
- post api("/projects/#{project.id}/protected_branches", user),
- name: branch_name, merge_access_level: 0
+ post post_endpoint, name: branch_name, merge_access_level: 0
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -161,8 +162,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and no one can push or merge' do
- post api("/projects/#{project.id}/protected_branches", user),
- name: branch_name, push_access_level: 0, merge_access_level: 0
+ post post_endpoint, name: branch_name, push_access_level: 0, merge_access_level: 0
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -171,7 +171,8 @@ describe API::ProtectedBranches do
end
it 'returns a 409 error if the same branch is protected twice' do
- post api("/projects/#{project.id}/protected_branches", user), name: protected_name
+ post post_endpoint, name: protected_name
+
expect(response).to have_gitlab_http_status(409)
end
@@ -179,10 +180,9 @@ describe API::ProtectedBranches do
let(:branch_name) { 'feature/*' }
it "protects multiple branches with a wildcard in the name" do
- post api("/projects/#{project.id}/protected_branches", user), name: branch_name
+ post post_endpoint, name: branch_name
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['name']).to eq(branch_name)
+ expect_protection_to_be_successful
expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER)
expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER)
end
@@ -195,7 +195,7 @@ describe API::ProtectedBranches do
end
it "returns a 403 error if guest" do
- post api("/projects/#{project.id}/protected_branches/", user), name: branch_name
+ post post_endpoint, name: branch_name
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 63175c40a18..015d4b9a491 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -54,7 +54,7 @@ describe API::Settings, 'Settings' do
dsa_key_restriction: 2048,
ecdsa_key_restriction: 384,
ed25519_key_restriction: 256,
- circuitbreaker_failure_wait_time: 2
+ circuitbreaker_check_interval: 2
expect(response).to have_gitlab_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
@@ -75,7 +75,7 @@ describe API::Settings, 'Settings' do
expect(json_response['dsa_key_restriction']).to eq(2048)
expect(json_response['ecdsa_key_restriction']).to eq(384)
expect(json_response['ed25519_key_restriction']).to eq(256)
- expect(json_response['circuitbreaker_failure_wait_time']).to eq(2)
+ expect(json_response['circuitbreaker_check_interval']).to eq(2)
end
end
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index decdd577226..3ee59014b5b 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -276,6 +276,89 @@ module Ci
end
end
+ context 'when "dependencies" keyword is specified' do
+ shared_examples 'not pick' do
+ it 'does not pick the build and drops the build' do
+ expect(subject).to be_nil
+ expect(pending_job.reload).to be_failed
+ expect(pending_job).to be_missing_dependency_failure
+ end
+ end
+
+ shared_examples 'validation is active' do
+ context 'when depended job has not been completed yet' do
+ let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ it_behaves_like 'not pick'
+ end
+
+ context 'when artifacts of depended job has been expired' do
+ let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ it_behaves_like 'not pick'
+ end
+
+ context 'when artifacts of depended job has been erased' do
+ let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
+
+ before do
+ pre_stage_job.erase
+ end
+
+ it_behaves_like 'not pick'
+ end
+ end
+
+ shared_examples 'validation is not active' do
+ context 'when depended job has not been completed yet' do
+ let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ it { expect(subject).to eq(pending_job) }
+ end
+
+ context 'when artifacts of depended job has been expired' do
+ let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
+
+ it { expect(subject).to eq(pending_job) }
+ end
+
+ context 'when artifacts of depended job has been erased' do
+ let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
+
+ before do
+ pre_stage_job.erase
+ end
+
+ it { expect(subject).to eq(pending_job) }
+ end
+ end
+
+ before do
+ stub_feature_flags(ci_disable_validates_dependencies: false)
+ end
+
+ let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
+ let!(:pending_job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['test'] } ) }
+
+ subject { execute(specific_runner) }
+
+ context 'when validates for dependencies is enabled' do
+ before do
+ stub_feature_flags(ci_disable_validates_dependencies: false)
+ end
+
+ it_behaves_like 'validation is active'
+ end
+
+ context 'when validates for dependencies is disabled' do
+ before do
+ stub_feature_flags(ci_disable_validates_dependencies: true)
+ end
+
+ it_behaves_like 'validation is not active'
+ end
+ end
+
def execute(runner)
described_class.new(runner).execute.build
end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 53862283a27..4057caca2ac 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -3,210 +3,253 @@ require 'spec_helper'
describe Projects::ForkService do
include ProjectForksHelper
let(:gitlab_shell) { Gitlab::Shell.new }
+ context 'when forking a new project' do
+ describe 'fork by user' do
+ before do
+ @from_user = create(:user)
+ @from_namespace = @from_user.namespace
+ avatar = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
+ @from_project = create(:project,
+ :repository,
+ creator_id: @from_user.id,
+ namespace: @from_namespace,
+ star_count: 107,
+ avatar: avatar,
+ description: 'wow such project')
+ @to_user = create(:user)
+ @to_namespace = @to_user.namespace
+ @from_project.add_user(@to_user, :developer)
+ end
- describe 'fork by user' do
- before do
- @from_user = create(:user)
- @from_namespace = @from_user.namespace
- avatar = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
- @from_project = create(:project,
- :repository,
- creator_id: @from_user.id,
- namespace: @from_namespace,
- star_count: 107,
- avatar: avatar,
- description: 'wow such project')
- @to_user = create(:user)
- @to_namespace = @to_user.namespace
- @from_project.add_user(@to_user, :developer)
- end
+ context 'fork project' do
+ context 'when forker is a guest' do
+ before do
+ @guest = create(:user)
+ @from_project.add_user(@guest, :guest)
+ end
+ subject { fork_project(@from_project, @guest) }
- context 'fork project' do
- context 'when forker is a guest' do
- before do
- @guest = create(:user)
- @from_project.add_user(@guest, :guest)
+ it { is_expected.not_to be_persisted }
+ it { expect(subject.errors[:forked_from_project_id]).to eq(['is forbidden']) }
end
- subject { fork_project(@from_project, @guest) }
- it { is_expected.not_to be_persisted }
- it { expect(subject.errors[:forked_from_project_id]).to eq(['is forbidden']) }
- end
+ describe "successfully creates project in the user namespace" do
+ let(:to_project) { fork_project(@from_project, @to_user, namespace: @to_user.namespace) }
- describe "successfully creates project in the user namespace" do
- let(:to_project) { fork_project(@from_project, @to_user, namespace: @to_user.namespace) }
-
- it { expect(to_project).to be_persisted }
- it { expect(to_project.errors).to be_empty }
- it { expect(to_project.owner).to eq(@to_user) }
- it { expect(to_project.namespace).to eq(@to_user.namespace) }
- it { expect(to_project.star_count).to be_zero }
- it { expect(to_project.description).to eq(@from_project.description) }
- it { expect(to_project.avatar.file).to be_exists }
-
- # This test is here because we had a bug where the from-project lost its
- # avatar after being forked.
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/26158
- it "after forking the from-project still has its avatar" do
- # If we do not fork the project first we cannot detect the bug.
- expect(to_project).to be_persisted
-
- expect(@from_project.avatar.file).to be_exists
- end
+ it { expect(to_project).to be_persisted }
+ it { expect(to_project.errors).to be_empty }
+ it { expect(to_project.owner).to eq(@to_user) }
+ it { expect(to_project.namespace).to eq(@to_user.namespace) }
+ it { expect(to_project.star_count).to be_zero }
+ it { expect(to_project.description).to eq(@from_project.description) }
+ it { expect(to_project.avatar.file).to be_exists }
- it 'flushes the forks count cache of the source project' do
- expect(@from_project.forks_count).to be_zero
+ # This test is here because we had a bug where the from-project lost its
+ # avatar after being forked.
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/26158
+ it "after forking the from-project still has its avatar" do
+ # If we do not fork the project first we cannot detect the bug.
+ expect(to_project).to be_persisted
- fork_project(@from_project, @to_user)
+ expect(@from_project.avatar.file).to be_exists
+ end
- expect(@from_project.forks_count).to eq(1)
- end
+ it 'flushes the forks count cache of the source project' do
+ expect(@from_project.forks_count).to be_zero
- it 'creates a fork network with the new project and the root project set' do
- to_project
- fork_network = @from_project.reload.fork_network
+ fork_project(@from_project, @to_user)
- expect(fork_network).not_to be_nil
- expect(fork_network.root_project).to eq(@from_project)
- expect(fork_network.projects).to contain_exactly(@from_project, to_project)
- end
- end
+ expect(@from_project.forks_count).to eq(1)
+ end
- context 'creating a fork of a fork' do
- let(:from_forked_project) { fork_project(@from_project, @to_user) }
- let(:other_namespace) do
- group = create(:group)
- group.add_owner(@to_user)
- group
- end
- let(:to_project) { fork_project(from_forked_project, @to_user, namespace: other_namespace) }
+ it 'creates a fork network with the new project and the root project set' do
+ to_project
+ fork_network = @from_project.reload.fork_network
- it 'sets the root of the network to the root project' do
- expect(to_project.fork_network.root_project).to eq(@from_project)
+ expect(fork_network).not_to be_nil
+ expect(fork_network.root_project).to eq(@from_project)
+ expect(fork_network.projects).to contain_exactly(@from_project, to_project)
+ end
end
- it 'sets the forked_from_project on the membership' do
- expect(to_project.fork_network_member.forked_from_project).to eq(from_forked_project)
+ context 'creating a fork of a fork' do
+ let(:from_forked_project) { fork_project(@from_project, @to_user) }
+ let(:other_namespace) do
+ group = create(:group)
+ group.add_owner(@to_user)
+ group
+ end
+ let(:to_project) { fork_project(from_forked_project, @to_user, namespace: other_namespace) }
+
+ it 'sets the root of the network to the root project' do
+ expect(to_project.fork_network.root_project).to eq(@from_project)
+ end
+
+ it 'sets the forked_from_project on the membership' do
+ expect(to_project.fork_network_member.forked_from_project).to eq(from_forked_project)
+ end
end
end
- end
- context 'project already exists' do
- it "fails due to validation, not transaction failure" do
- @existing_project = create(:project, :repository, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace)
- @to_project = fork_project(@from_project, @to_user, namespace: @to_namespace)
- expect(@existing_project).to be_persisted
+ context 'project already exists' do
+ it "fails due to validation, not transaction failure" do
+ @existing_project = create(:project, :repository, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace)
+ @to_project = fork_project(@from_project, @to_user, namespace: @to_namespace)
+ expect(@existing_project).to be_persisted
- expect(@to_project).not_to be_persisted
- expect(@to_project.errors[:name]).to eq(['has already been taken'])
- expect(@to_project.errors[:path]).to eq(['has already been taken'])
+ expect(@to_project).not_to be_persisted
+ expect(@to_project.errors[:name]).to eq(['has already been taken'])
+ expect(@to_project.errors[:path]).to eq(['has already been taken'])
+ end
end
- end
- context 'repository already exists' do
- let(:repository_storage) { 'default' }
- let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
+ context 'repository already exists' do
+ let(:repository_storage) { 'default' }
+ let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
- before do
- gitlab_shell.add_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
- end
+ before do
+ gitlab_shell.add_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
+ end
- after do
- gitlab_shell.remove_repository(repository_storage_path, "#{@to_user.namespace.full_path}/#{@from_project.path}")
- end
+ after do
+ gitlab_shell.remove_repository(repository_storage_path, "#{@to_user.namespace.full_path}/#{@from_project.path}")
+ end
- it 'does not allow creation' do
- to_project = fork_project(@from_project, @to_user, namespace: @to_user.namespace)
+ it 'does not allow creation' do
+ to_project = fork_project(@from_project, @to_user, namespace: @to_user.namespace)
- expect(to_project).not_to be_persisted
- expect(to_project.errors.messages).to have_key(:base)
- expect(to_project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
+ expect(to_project).not_to be_persisted
+ expect(to_project.errors.messages).to have_key(:base)
+ expect(to_project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
+ end
end
- end
- context 'GitLab CI is enabled' do
- it "forks and enables CI for fork" do
- @from_project.enable_ci
- @to_project = fork_project(@from_project, @to_user)
- expect(@to_project.builds_enabled?).to be_truthy
+ context 'GitLab CI is enabled' do
+ it "forks and enables CI for fork" do
+ @from_project.enable_ci
+ @to_project = fork_project(@from_project, @to_user)
+ expect(@to_project.builds_enabled?).to be_truthy
+ end
end
- end
- context "when project has restricted visibility level" do
- context "and only one visibility level is restricted" do
- before do
- @from_project.update_attributes(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
- stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+ context "when project has restricted visibility level" do
+ context "and only one visibility level is restricted" do
+ before do
+ @from_project.update_attributes(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+ end
+
+ it "creates fork with highest allowed level" do
+ forked_project = fork_project(@from_project, @to_user)
+
+ expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
end
- it "creates fork with highest allowed level" do
- forked_project = fork_project(@from_project, @to_user)
+ context "and all visibility levels are restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE])
+ end
+
+ it "creates fork with private visibility levels" do
+ forked_project = fork_project(@from_project, @to_user)
- expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
end
end
+ end
- context "and all visibility levels are restricted" do
- before do
- stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE])
+ describe 'fork to namespace' do
+ before do
+ @group_owner = create(:user)
+ @developer = create(:user)
+ @project = create(:project, :repository,
+ creator_id: @group_owner.id,
+ star_count: 777,
+ description: 'Wow, such a cool project!')
+ @group = create(:group)
+ @group.add_user(@group_owner, GroupMember::OWNER)
+ @group.add_user(@developer, GroupMember::DEVELOPER)
+ @project.add_user(@developer, :developer)
+ @project.add_user(@group_owner, :developer)
+ @opts = { namespace: @group }
+ end
+
+ context 'fork project for group' do
+ it 'group owner successfully forks project into the group' do
+ to_project = fork_project(@project, @group_owner, @opts)
+
+ expect(to_project).to be_persisted
+ expect(to_project.errors).to be_empty
+ expect(to_project.owner).to eq(@group)
+ expect(to_project.namespace).to eq(@group)
+ expect(to_project.name).to eq(@project.name)
+ expect(to_project.path).to eq(@project.path)
+ expect(to_project.description).to eq(@project.description)
+ expect(to_project.star_count).to be_zero
end
+ end
- it "creates fork with private visibility levels" do
- forked_project = fork_project(@from_project, @to_user)
+ context 'fork project for group when user not owner' do
+ it 'group developer fails to fork project into the group' do
+ to_project = fork_project(@project, @developer, @opts)
+ expect(to_project.errors[:namespace]).to eq(['is not valid'])
+ end
+ end
- expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ context 'project already exists in group' do
+ it 'fails due to validation, not transaction failure' do
+ existing_project = create(:project, :repository,
+ name: @project.name,
+ namespace: @group)
+ to_project = fork_project(@project, @group_owner, @opts)
+ expect(existing_project.persisted?).to be_truthy
+ expect(to_project.errors[:name]).to eq(['has already been taken'])
+ expect(to_project.errors[:path]).to eq(['has already been taken'])
end
end
end
end
- describe 'fork to namespace' do
- before do
- @group_owner = create(:user)
- @developer = create(:user)
- @project = create(:project, :repository,
- creator_id: @group_owner.id,
- star_count: 777,
- description: 'Wow, such a cool project!')
- @group = create(:group)
- @group.add_user(@group_owner, GroupMember::OWNER)
- @group.add_user(@developer, GroupMember::DEVELOPER)
- @project.add_user(@developer, :developer)
- @project.add_user(@group_owner, :developer)
- @opts = { namespace: @group }
+ context 'when linking fork to an existing project' do
+ let(:fork_from_project) { create(:project, :public) }
+ let(:fork_to_project) { create(:project, :public) }
+ let(:user) { create(:user) }
+
+ subject { described_class.new(fork_from_project, user) }
+
+ def forked_from_project(project)
+ project.fork_network_member&.forked_from_project
end
- context 'fork project for group' do
- it 'group owner successfully forks project into the group' do
- to_project = fork_project(@project, @group_owner, @opts)
-
- expect(to_project).to be_persisted
- expect(to_project.errors).to be_empty
- expect(to_project.owner).to eq(@group)
- expect(to_project.namespace).to eq(@group)
- expect(to_project.name).to eq(@project.name)
- expect(to_project.path).to eq(@project.path)
- expect(to_project.description).to eq(@project.description)
- expect(to_project.star_count).to be_zero
+ context 'if project is already forked' do
+ it 'does not create fork relation' do
+ allow(fork_to_project).to receive(:forked?).and_return(true)
+ expect(forked_from_project(fork_to_project)).to be_nil
+ expect(subject.execute(fork_to_project)).to be_nil
+ expect(forked_from_project(fork_to_project)).to be_nil
end
end
- context 'fork project for group when user not owner' do
- it 'group developer fails to fork project into the group' do
- to_project = fork_project(@project, @developer, @opts)
- expect(to_project.errors[:namespace]).to eq(['is not valid'])
+ context 'if project is not forked' do
+ it 'creates fork relation' do
+ expect(fork_to_project.forked?).to be false
+ expect(forked_from_project(fork_to_project)).to be_nil
+
+ subject.execute(fork_to_project)
+
+ expect(fork_to_project.forked?).to be true
+ expect(forked_from_project(fork_to_project)).to eq fork_from_project
+ expect(fork_to_project.forked_from_project).to eq fork_from_project
end
- end
- context 'project already exists in group' do
- it 'fails due to validation, not transaction failure' do
- existing_project = create(:project, :repository,
- name: @project.name,
- namespace: @group)
- to_project = fork_project(@project, @group_owner, @opts)
- expect(existing_project.persisted?).to be_truthy
- expect(to_project.errors[:name]).to eq(['has already been taken'])
- expect(to_project.errors[:path]).to eq(['has already been taken'])
+ it 'flushes the forks count cache of the source project' do
+ expect(fork_from_project.forks_count).to be_zero
+
+ subject.execute(fork_to_project)
+
+ expect(fork_from_project.forks_count).to eq(1)
end
end
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index fcd71857af3..d887f70efae 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -199,24 +199,53 @@ describe Projects::UpdateService do
end
describe '#run_auto_devops_pipeline?' do
- subject { described_class.new(project, user, params).run_auto_devops_pipeline? }
+ subject { described_class.new(project, user).run_auto_devops_pipeline? }
- context 'when neither pipeline setting is true' do
- let(:params) { {} }
+ context 'when master contains a .gitlab-ci.yml file' do
+ before do
+ allow(project.repository).to receive(:gitlab_ci_yml).and_return("script: ['test']")
+ end
it { is_expected.to eq(false) }
end
- context 'when run_auto_devops_pipeline_explicit is true' do
- let(:params) { { run_auto_devops_pipeline_explicit: 'true' } }
+ context 'when auto devops is explicitly enabled' do
+ before do
+ project.create_auto_devops!(enabled: true)
+ end
it { is_expected.to eq(true) }
end
- context 'when run_auto_devops_pipeline_implicit is true' do
- let(:params) { { run_auto_devops_pipeline_implicit: 'true' } }
+ context 'when auto devops is explicitly disabled' do
+ before do
+ project.create_auto_devops!(enabled: false)
+ end
- it { is_expected.to eq(true) }
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when auto devops is set to instance setting' do
+ before do
+ project.create_auto_devops!(enabled: nil)
+ allow(project.auto_devops).to receive(:previous_changes).and_return('enabled' => true)
+ end
+
+ context 'when auto devops is enabled system-wide' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when auto devops is disabled system-wide' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index a918383ecd2..47412110b4b 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -692,9 +692,9 @@ describe SystemNoteService do
describe '.new_commit_summary' do
it 'escapes HTML titles' do
commit = double(title: '<pre>This is a test</pre>', short_id: '12345678')
- escaped = '* 12345678 - &lt;pre&gt;This is a test&lt;&#x2F;pre&gt;'
+ escaped = '&lt;pre&gt;This is a test&lt;&#x2F;pre&gt;'
- expect(described_class.new_commit_summary([commit])).to eq([escaped])
+ expect(described_class.new_commit_summary([commit])).to all(match(%r[- #{escaped}]))
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 242a2230b67..f94fb8733d5 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -121,18 +121,6 @@ RSpec.configure do |config|
reset_delivered_emails!
end
- # Stub the `ForkedStorageCheck.storage_available?` method unless
- # `:broken_storage` metadata is defined
- #
- # This check can be slow and is unnecessary in a test environment where we
- # know the storage is available, because we create it at runtime
- config.before(:example) do |example|
- unless example.metadata[:broken_storage]
- allow(Gitlab::Git::Storage::ForkedStorageCheck)
- .to receive(:storage_available?).and_return(true)
- end
- end
-
config.around(:each, :use_clean_rails_memory_store_caching) do |example|
caching_store = Rails.cache
Rails.cache = ActiveSupport::Cache::MemoryStore.new
diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb
index dabf0db7666..8a073e58db8 100644
--- a/spec/support/google_api/cloud_platform_helpers.rb
+++ b/spec/support/google_api/cloud_platform_helpers.rb
@@ -63,7 +63,7 @@ module GoogleApi
##
# gcloud container clusters create
- # https://cloud.google.com/container-engine/reference/rest/v1/projects.zones.clusters/create
+ # https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.zones.clusters/create
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def cloud_platform_cluster_body(**options)
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
new file mode 100644
index 00000000000..935c08221e0
--- /dev/null
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -0,0 +1,240 @@
+shared_examples 'handle uploads' do
+ let(:user) { create(:user) }
+ let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
+ let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+
+ describe "POST #create" do
+ context 'when a user is not authorized to upload a file' do
+ it 'returns 404 status' do
+ post :create, params.merge(file: jpg, format: :json)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when a user can upload a file' do
+ before do
+ sign_in(user)
+ model.add_developer(user)
+ end
+
+ context "without params['file']" do
+ it "returns an error" do
+ post :create, params.merge(format: :json)
+
+ expect(response).to have_gitlab_http_status(422)
+ end
+ end
+
+ context 'with valid image' do
+ before do
+ post :create, params.merge(file: jpg, format: :json)
+ end
+
+ it 'returns a content with original filename, new link, and correct type.' do
+ expect(response.body).to match '\"alt\":\"rails_sample\"'
+ expect(response.body).to match "\"url\":\"/uploads"
+ end
+
+ # NOTE: This is as close as we're getting to an Integration test for this
+ # behavior. We're avoiding a proper Feature test because those should be
+ # testing things entirely user-facing, which the Upload model is very much
+ # not.
+ it 'creates a corresponding Upload record' do
+ upload = Upload.last
+
+ aggregate_failures do
+ expect(upload).to exist
+ expect(upload.model).to eq(model)
+ end
+ end
+ end
+
+ context 'with valid non-image file' do
+ before do
+ post :create, params.merge(file: txt, format: :json)
+ end
+
+ it 'returns a content with original filename, new link, and correct type.' do
+ expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
+ expect(response.body).to match "\"url\":\"/uploads"
+ end
+ end
+ end
+ end
+
+ describe "GET #show" do
+ let(:show_upload) do
+ get :show, params.merge(secret: "123456", filename: "image.jpg")
+ end
+
+ context "when the model is public" do
+ before do
+ model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ context "when not signed in" do
+ context "when the file exists" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
+ allow(jpg).to receive(:exists?).and_return(true)
+ end
+
+ it "responds with status 200" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the file doesn't exist" do
+ it "responds with status 404" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when the file exists" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
+ allow(jpg).to receive(:exists?).and_return(true)
+ end
+
+ it "responds with status 200" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the file doesn't exist" do
+ it "responds with status 404" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+
+ context "when the model is private" do
+ before do
+ model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context "when not signed in" do
+ context "when the file exists" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
+ allow(jpg).to receive(:exists?).and_return(true)
+ end
+
+ context "when the file is an image" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
+ end
+
+ it "responds with status 200" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the file is not an image" do
+ it "redirects to the sign in page" do
+ show_upload
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context "when the file doesn't exist" do
+ it "redirects to the sign in page" do
+ show_upload
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when the user has access to the project" do
+ before do
+ model.add_developer(user)
+ end
+
+ context "when the file exists" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
+ allow(jpg).to receive(:exists?).and_return(true)
+ end
+
+ it "responds with status 200" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the file doesn't exist" do
+ it "responds with status 404" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context "when the user doesn't have access to the model" do
+ context "when the file exists" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
+ allow(jpg).to receive(:exists?).and_return(true)
+ end
+
+ context "when the file is an image" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
+ end
+
+ it "responds with status 200" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the file is not an image" do
+ it "responds with status 404" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context "when the file doesn't exist" do
+ it "responds with status 404" do
+ show_upload
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index f3deae0f455..f9121cce985 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -12,6 +12,25 @@ RSpec.configure do |config|
raise GRPC::Unavailable.new('Gitaly broken in this spec')
end
- Gitlab::Git::Storage::CircuitBreaker.reset_all!
+ # Track the maximum number of failures
+ first_failure = Time.parse("2017-11-14 17:52:30")
+ last_failure = Time.parse("2017-11-14 18:54:37")
+ failure_count = Gitlab::CurrentSettings
+ .current_application_settings
+ .circuitbreaker_failure_count_threshold + 1
+ cache_key = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}broken:#{Gitlab::Environment.hostname}"
+
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.pipelined do
+ redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
+ redis.hset(cache_key, :first_failure, first_failure.to_i)
+ redis.hset(cache_key, :last_failure, last_failure.to_i)
+ redis.hset(cache_key, :failure_count, failure_count.to_i)
+ end
+ end
+ end
+
+ config.after(:each, :broken_storage) do
+ Gitlab::Git::Storage.redis.with(&:flushall)
end
end
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index 4ead78529c3..b36cf3c544c 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -43,6 +43,8 @@ module StubConfiguration
end
def stub_storage_settings(messages)
+ messages.deep_stringify_keys!
+
# Default storage is always required
messages['default'] ||= Gitlab.config.repositories.storages.default
messages.each do |storage_name, storage_settings|
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index bf2e11bc360..b41c3b3958a 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -212,7 +212,7 @@ describe 'gitlab:app namespace rake task' do
# Avoid asking gitaly about the root ref (which will fail beacuse of the
# mocked storages)
- allow_any_instance_of(Repository).to receive(:empty_repo?).and_return(false)
+ allow_any_instance_of(Repository).to receive(:empty?).and_return(false)
end
after do
diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb
new file mode 100644
index 00000000000..c6c4500c179
--- /dev/null
+++ b/spec/uploaders/namespace_file_uploader_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe NamespaceFileUploader do
+ let(:group) { build_stubbed(:group) }
+ let(:uploader) { described_class.new(group) }
+
+ describe "#store_dir" do
+ it "stores in the namespace id directory" do
+ expect(uploader.store_dir).to include(group.id.to_s)
+ end
+ end
+
+ describe ".absolute_path" do
+ it "stores in thecorrect directory" do
+ upload_record = create(:upload, :namespace_upload, model: group)
+
+ expect(described_class.absolute_path(upload_record))
+ .to include("-/system/namespace/#{group.id}")
+ end
+ end
+end
diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb
index 32c95c6bb0d..a9c32122600 100644
--- a/spec/views/projects/commit/show.html.haml_spec.rb
+++ b/spec/views/projects/commit/show.html.haml_spec.rb
@@ -2,14 +2,15 @@ require 'spec_helper'
describe 'projects/commit/show.html.haml' do
let(:project) { create(:project, :repository) }
+ let(:commit) { project.commit }
before do
assign(:project, project)
assign(:repository, project.repository)
- assign(:commit, project.commit)
- assign(:noteable, project.commit)
+ assign(:commit, commit)
+ assign(:noteable, commit)
assign(:notes, [])
- assign(:diffs, project.commit.diffs)
+ assign(:diffs, commit.diffs)
allow(view).to receive(:current_user).and_return(nil)
allow(view).to receive(:can?).and_return(false)
@@ -43,4 +44,19 @@ describe 'projects/commit/show.html.haml' do
expect(rendered).not_to have_selector('.limit-container-width')
end
end
+
+ context 'in the context of a merge request' do
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+ before do
+ assign(:merge_request, merge_request)
+ render
+ end
+
+ it 'shows that it is in the context of a merge request' do
+ merge_request_url = diffs_project_merge_request_url(project, merge_request, commit_id: commit.id)
+ expect(rendered).to have_content("This commit is part of merge request")
+ expect(rendered).to have_link(merge_request.to_reference, merge_request_url)
+ end
+ end
end
diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
index efed2e02a1b..3ca67114558 100644
--- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -25,8 +25,8 @@ describe 'projects/merge_requests/_commits.html.haml' do
it 'shows commits from source project' do
render
- commit = source_project.commit(merge_request.source_branch)
- href = project_commit_path(source_project, commit)
+ commit = merge_request.commits.first # HEAD
+ href = diffs_project_merge_request_path(target_project, merge_request, commit_id: commit)
expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
end
diff --git a/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb b/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb
new file mode 100644
index 00000000000..e7c40421f1f
--- /dev/null
+++ b/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe 'projects/merge_requests/diffs/_diffs.html.haml' do
+ include Devise::Test::ControllerHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project, author: user) }
+
+ before do
+ allow(view).to receive(:url_for).and_return(controller.request.fullpath)
+
+ assign(:merge_request, merge_request)
+ assign(:environment, merge_request.environments_for(user).last)
+ assign(:diffs, merge_request.diffs)
+ assign(:merge_request_diffs, merge_request.diffs)
+ assign(:diff_notes_disabled, true) # disable note creation
+ assign(:use_legacy_diff_notes, false)
+ assign(:grouped_diff_discussions, {})
+ assign(:notes, [])
+ end
+
+ context 'for a commit' do
+ let(:commit) { merge_request.commits.last }
+
+ before do
+ assign(:commit, commit)
+ end
+
+ it "shows the commit scope" do
+ render
+
+ expect(rendered).to have_content "Only comments from the following commit are shown below"
+ end
+ end
+end