summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml3
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml12
-rw-r--r--.rubocop.yml14
-rw-r--r--CHANGELOG.md60
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--PHILOSOPHY.md5
-rw-r--r--README.md13
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_math.js146
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js2
-rw-r--r--app/assets/javascripts/droplab/drop_lab.js4
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue20
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue6
-rw-r--r--app/assets/javascripts/ide/components/error_message.vue2
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue3
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue4
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue2
-rw-r--r--app/assets/javascripts/issue_show/services/index.js4
-rw-r--r--app/assets/javascripts/main.js2
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue4
-rw-r--r--app/assets/javascripts/tracking.js28
-rw-r--r--app/assets/stylesheets/csslab.scss1
-rw-r--r--app/assets/stylesheets/framework/forms.scss24
-rw-r--r--app/assets/stylesheets/framework/typography.scss14
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/search.scss10
-rw-r--r--app/assets/stylesheets/pages/todos.scss16
-rw-r--r--app/controllers/boards/lists_controller.rb24
-rw-r--r--app/controllers/concerns/issuable_actions.rb30
-rw-r--r--app/controllers/concerns/notes_actions.rb2
-rw-r--r--app/controllers/concerns/uploads_actions.rb4
-rw-r--r--app/controllers/groups/runners_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests_controller.rb16
-rw-r--r--app/controllers/sessions_controller.rb44
-rw-r--r--app/controllers/uploads_controller.rb6
-rw-r--r--app/finders/group_projects_finder.rb12
-rw-r--r--app/finders/members_finder.rb35
-rw-r--r--app/helpers/application_settings_helper.rb5
-rw-r--r--app/helpers/emails_helper.rb4
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/todos_helper.rb18
-rw-r--r--app/mailers/emails/issues.rb4
-rw-r--r--app/models/application_setting.rb26
-rw-r--r--app/models/application_setting_implementation.rb28
-rw-r--r--app/models/ci/job_artifact.rb2
-rw-r--r--app/models/ci/pipeline.rb1
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/commit.rb7
-rw-r--r--app/models/concerns/issuable.rb1
-rw-r--r--app/models/concerns/noteable.rb4
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/label.rb8
-rw-r--r--app/models/lfs_object.rb1
-rw-r--r--app/models/list.rb54
-rw-r--r--app/models/list_user_preference.rb10
-rw-r--r--app/models/note.rb5
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/project_services/jira_service.rb7
-rw-r--r--app/models/remote_mirror.rb1
-rw-r--r--app/models/repository.rb12
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/todo.rb4
-rw-r--r--app/models/user.rb7
-rw-r--r--app/policies/issue_policy.rb9
-rw-r--r--app/policies/merge_request_policy.rb6
-rw-r--r--app/serializers/merge_request_noteable_entity.rb53
-rw-r--r--app/serializers/merge_request_poll_widget_entity.rb10
-rw-r--r--app/serializers/merge_request_serializer.rb2
-rw-r--r--app/serializers/merge_request_widget_entity.rb12
-rw-r--r--app/services/application_settings/update_service.rb15
-rw-r--r--app/services/base_count_service.rb2
-rw-r--r--app/services/base_service.rb4
-rw-r--r--app/services/boards/lists/list_service.rb2
-rw-r--r--app/services/boards/lists/update_service.rb56
-rw-r--r--app/services/ci/create_pipeline_service.rb3
-rw-r--r--app/services/clusters/applications/check_installation_progress_service.rb35
-rw-r--r--app/services/clusters/applications/check_progress_service.rb50
-rw-r--r--app/services/clusters/applications/check_uninstall_progress_service.rb33
-rw-r--r--app/services/create_snippet_service.rb2
-rw-r--r--app/services/groups/create_service.rb4
-rw-r--r--app/services/projects/create_service.rb27
-rw-r--r--app/services/projects/lfs_pointers/lfs_link_service.rb29
-rw-r--r--app/services/system_note_service.rb10
-rw-r--r--app/services/todo_service.rb6
-rw-r--r--app/services/update_snippet_service.rb2
-rw-r--r--app/uploaders/personal_file_uploader.rb4
-rw-r--r--app/views/admin/application_settings/_ip_limits.html.haml8
-rw-r--r--app/views/admin/application_settings/_spam.html.haml13
-rw-r--r--app/views/admin/application_settings/network.html.haml2
-rw-r--r--app/views/admin/application_settings/reporting.html.haml4
-rw-r--r--app/views/dashboard/todos/_todo.html.haml34
-rw-r--r--app/views/devise/sessions/_new_base.html.haml2
-rw-r--r--app/views/groups/_group_admin_settings.html.haml2
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/settings/_permissions.html.haml4
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/layouts/_snowplow.html.haml21
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/notify/new_issue_email.text.erb10
-rw-r--r--app/views/notify/new_merge_request_email.text.erb4
-rw-r--r--app/views/projects/_merge_request_merge_checks_settings.html.haml2
-rw-r--r--app/views/projects/_merge_request_merge_method_settings.html.haml6
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/blob/preview.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_markup.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml2
-rw-r--r--app/views/projects/deployments/_deployment.html.haml3
-rw-r--r--app/views/projects/merge_requests/show.html.haml13
-rw-r--r--app/views/projects/milestones/_form.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml2
-rw-r--r--app/views/projects/services/_form.html.haml2
-rw-r--r--app/views/projects/services/_index.html.haml12
-rw-r--r--app/views/projects/services/edit.html.haml6
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml28
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_help.html.haml8
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml2
-rw-r--r--app/views/projects/services/slack_slash_commands/_help.html.haml44
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml4
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--app/views/shared/boards/_show.html.haml2
-rw-r--r--app/views/shared/empty_states/_profile_tabs.html.haml2
-rw-r--r--app/views/shared/issuable/_close_reopen_button.html.haml2
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/views/shared/issuable/_nav.html.haml2
-rw-r--r--app/views/shared/runners/_form.html.haml5
-rw-r--r--changelogs/unreleased/35060-remove-token-field.yml5
-rw-r--r--changelogs/unreleased/56130-operations-environments-shows-incorrect-deployment-date-for-manual-.yml6
-rw-r--r--changelogs/unreleased/57538-not-null-constraint-on-users-private-profile.yml5
-rw-r--r--changelogs/unreleased/62284-follow-up-from-resolve-api-to-get-all-project-group-members-returns-duplicates.yml5
-rw-r--r--changelogs/unreleased/62673-clean-note-app-tests.yml5
-rw-r--r--changelogs/unreleased/63262-notes-are-persisted-with-the-user-s-locale.yml5
-rw-r--r--changelogs/unreleased/64251-branch-name-set-cache.yml5
-rw-r--r--changelogs/unreleased/65063-Embeded-metrics-inconsistent-styles.yml5
-rw-r--r--changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml5
-rw-r--r--changelogs/unreleased/bump-pages-1-8.yml5
-rw-r--r--changelogs/unreleased/ce-60465-prevent-comments-on-private-mrs.yml3
-rw-r--r--changelogs/unreleased/ce-slack-close-command.yml5
-rw-r--r--changelogs/unreleased/ce-xanf-move-auto-merge-failed-to-jest.yml5
-rw-r--r--changelogs/unreleased/cluster_deployments.yml5
-rw-r--r--changelogs/unreleased/fix-create-milestone-btn-success.yml5
-rw-r--r--changelogs/unreleased/fix-dropdown-closing.yml5
-rw-r--r--changelogs/unreleased/fix-search-input-dropdown.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-13698-override-params.yml5
-rw-r--r--changelogs/unreleased/handle-invalid-mirror-url.yml5
-rw-r--r--changelogs/unreleased/id-optimize-sql-requests-mr-show.yml5
-rw-r--r--changelogs/unreleased/issue_40630.yml5
-rw-r--r--changelogs/unreleased/new-project-milestone-primary-button.yml5
-rw-r--r--changelogs/unreleased/runner-chart-repo-use-new-location.yml5
-rw-r--r--changelogs/unreleased/security-59549-add-capcha-for-failed-logins.yml5
-rw-r--r--changelogs/unreleased/security-61974-limit-issue-comment-size-2.yml5
-rw-r--r--changelogs/unreleased/security-61974-limit-issue-comment-size.yml5
-rw-r--r--changelogs/unreleased/security-64711-fix-commit-todos.yml5
-rw-r--r--changelogs/unreleased/security-ci-metrics-permissions.yml6
-rw-r--r--changelogs/unreleased/security-enable-image-proxy.yml5
-rw-r--r--changelogs/unreleased/security-epic-notes-api-reveals-historical-info-ce-master.yml5
-rw-r--r--changelogs/unreleased/security-exposed-default-branch.yml5
-rw-r--r--changelogs/unreleased/security-fix-html-injection-for-label-description-ce-master.yml5
-rw-r--r--changelogs/unreleased/security-fix-markdown-xss.yml5
-rw-r--r--changelogs/unreleased/security-fix_jira_ssrf_vulnerability.yml5
-rw-r--r--changelogs/unreleased/security-gitaly-1-61-0.yml5
-rw-r--r--changelogs/unreleased/security-group-runners-permissions.yml5
-rw-r--r--changelogs/unreleased/security-hide_merge_request_ids_on_emails.yml5
-rw-r--r--changelogs/unreleased/security-id-filter-timeline-activities-for-guests.yml5
-rw-r--r--changelogs/unreleased/security-katex-dos-master.yml5
-rw-r--r--changelogs/unreleased/security-mr-head-pipeline-leak.yml5
-rw-r--r--changelogs/unreleased/security-personal-snippets.yml5
-rw-r--r--changelogs/unreleased/security-project-import-bypass.yml5
-rw-r--r--changelogs/unreleased/security-sarcila-fix-weak-session-management.yml6
-rw-r--r--changelogs/unreleased/security-ssrf-kubernetes-dns.yml5
-rw-r--r--changelogs/unreleased/sh-add-delete-confirmation.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-move-api.yml5
-rw-r--r--changelogs/unreleased/sh-fix-snippet-visibility-api.yml5
-rw-r--r--changelogs/unreleased/sh-lfs-object-batches.yml5
-rw-r--r--changelogs/unreleased/sh-support-content-for-snippets-api.yml5
-rw-r--r--changelogs/unreleased/sh-upgrade-mermaid-8-2-4.yml5
-rw-r--r--changelogs/unreleased/ss-add-board-name-to-page-title.yml5
-rw-r--r--changelogs/unreleased/tc-cleanup-issue-created-text-mail.yml5
-rw-r--r--changelogs/unreleased/todos-include-issue-mr-titles.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-8-0.yml5
-rw-r--r--config/application.rb1
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/asset_proxy_settings.rb6
-rw-r--r--config/initializers/fill_shards.rb4
-rw-r--r--config/initializers/peek.rb1
-rw-r--r--config/initializers/rest-client-hostname_override.rb49
-rw-r--r--config/initializers/warden.rb1
-rw-r--r--config/routes.rb1
-rw-r--r--config/routes/uploads.rb4
-rw-r--r--db/migrate/20190219201635_add_asset_proxy_settings.rb16
-rw-r--r--db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb12
-rw-r--r--db/migrate/20190816151221_add_active_jobs_limit_to_plans.rb17
-rw-r--r--db/migrate/20190819131155_add_cluster_status_index_to_deployments.rb17
-rw-r--r--db/migrate/20190822181528_create_list_user_preferences.rb16
-rw-r--r--db/migrate/20190826090628_remove_redundant_deployments_index.rb18
-rw-r--r--db/migrate/20190828083843_add_index_to_ci_job_artifacts_on_project_id_for_security_reports.rb22
-rw-r--r--db/post_migrate/20190725080128_set_not_null_on_users_private_profile.rb26
-rw-r--r--db/schema.rb27
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md4
-rw-r--r--doc/administration/auth/okta.md16
-rw-r--r--doc/administration/geo/disaster_recovery/index.md2
-rw-r--r--doc/administration/geo/replication/troubleshooting.md4
-rw-r--r--doc/administration/gitaly/index.md17
-rw-r--r--doc/administration/high_availability/gitlab.md27
-rw-r--r--doc/administration/integration/plantuml.md3
-rw-r--r--doc/administration/logs.md9
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md11
-rw-r--r--doc/administration/pages/source.md16
-rw-r--r--doc/administration/raketasks/github_import.md2
-rw-r--r--doc/administration/raketasks/ldap.md4
-rw-r--r--doc/administration/raketasks/maintenance.md4
-rw-r--r--doc/administration/raketasks/uploads/migrate.md2
-rw-r--r--doc/administration/raketasks/uploads/sanitize.md2
-rw-r--r--doc/administration/troubleshooting/elasticsearch.md6
-rw-r--r--doc/administration/troubleshooting/sidekiq.md15
-rw-r--r--doc/api/README.md4
-rw-r--r--doc/api/epics.md4
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/api/groups.md1
-rw-r--r--doc/api/issues.md4
-rw-r--r--doc/api/merge_requests.md4
-rw-r--r--doc/api/notes.md16
-rw-r--r--doc/api/settings.md10
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/ci/directed_acyclic_graph/index.md2
-rw-r--r--doc/ci/examples/license_management.md4
-rw-r--r--doc/ci/jenkins/index.md8
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md2
-rw-r--r--doc/ci/runners/README.md3
-rw-r--r--doc/ci/services/mysql.md4
-rw-r--r--doc/ci/triggers/README.md5
-rw-r--r--doc/ci/yaml/README.md7
-rw-r--r--doc/development/contributing/index.md2
-rw-r--r--doc/development/documentation/index.md2
-rw-r--r--doc/development/documentation/structure.md2
-rw-r--r--doc/development/documentation/styleguide.md14
-rw-r--r--doc/development/ee_features.md2
-rw-r--r--doc/development/fe_guide/performance.md2
-rw-r--r--doc/development/go_guide/index.md2
-rw-r--r--doc/development/i18n/translation.md1
-rw-r--r--doc/development/new_fe_guide/dependencies.md2
-rw-r--r--doc/development/new_fe_guide/development/testing.md1
-rw-r--r--doc/development/testing_guide/best_practices.md2
-rw-r--r--doc/development/testing_guide/end_to_end/quick_start_guide.md2
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md2
-rw-r--r--doc/development/testing_guide/index.md4
-rw-r--r--doc/development/testing_guide/review_apps.md2
-rw-r--r--doc/development/testing_guide/testing_levels.md2
-rw-r--r--doc/development/ux_guide/animation.md4
-rw-r--r--doc/development/ux_guide/illustrations.md4
-rw-r--r--doc/gitlab-basics/add-merge-request.md2
-rw-r--r--doc/gitlab-basics/create-project.md2
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md2
-rw-r--r--doc/install/azure/index.md3
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/integration/slash_commands.md1
-rw-r--r--doc/legal/corporate_contributor_license_agreement.md2
-rw-r--r--doc/migrate_ci_to_ce/README.md4
-rw-r--r--doc/push_rules/push_rules.md6
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/security/asset_proxy.md28
-rw-r--r--doc/security/information_exclusivity.md2
-rw-r--r--doc/security/rack_attack.md9
-rw-r--r--doc/topics/autodevops/index.md77
-rw-r--r--doc/topics/git/partial_clone.md2
-rw-r--r--doc/university/process/README.md4
-rw-r--r--doc/university/training/gitlab_flow.md4
-rw-r--r--doc/university/training/topics/git_intro.md2
-rw-r--r--doc/university/training/user_training.md2
-rw-r--r--doc/update/patch_versions.md6
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md3
-rw-r--r--doc/update/upgrading_from_source.md17
-rw-r--r--doc/update/upgrading_postgresql_using_slony.md2
-rw-r--r--doc/user/admin_area/license.md2
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md2
-rw-r--r--doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md2
-rw-r--r--doc/user/admin_area/settings/terms.md3
-rw-r--r--doc/user/admin_area/settings/third_party_offers.md3
-rw-r--r--doc/user/application_security/container_scanning/index.md4
-rw-r--r--doc/user/application_security/index.md2
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance.png (renamed from doc/user/application_security/license_management/img/license_management.png)bin5184 -> 5184 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_add_license.png (renamed from doc/user/application_security/license_management/img/license_management_add_license.png)bin24247 -> 24247 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_decision.png (renamed from doc/user/application_security/license_management/img/license_management_decision.png)bin5975 -> 5975 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab.png (renamed from doc/user/application_security/license_management/img/license_management_pipeline_tab.png)bin12115 -> 12115 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_search.png (renamed from doc/user/application_security/license_management/img/license_management_search.png)bin28237 -> 28237 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_settings.png (renamed from doc/user/application_security/license_management/img/license_management_settings.png)bin44790 -> 44790 bytes
-rw-r--r--doc/user/application_security/license_compliance/index.md243
-rw-r--r--doc/user/application_security/license_management/index.md244
-rw-r--r--doc/user/application_security/security_dashboard/index.md3
-rw-r--r--doc/user/clusters/applications.md5
-rw-r--r--doc/user/discussions/index.md2
-rw-r--r--doc/user/gitlab_com/index.md13
-rw-r--r--doc/user/group/index.md4
-rw-r--r--doc/user/markdown.md7
-rw-r--r--doc/user/permissions.md38
-rw-r--r--doc/user/project/clusters/index.md2
-rw-r--r--doc/user/project/code_owners.md2
-rw-r--r--doc/user/project/img/protected_branches_devs_can_push.pngbin11221 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_devs_can_push_v12_3.pngbin0 -> 11941 bytes
-rw-r--r--doc/user/project/img/protected_branches_list.pngbin6933 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_list_v12_3.pngbin0 -> 8774 bytes
-rw-r--r--doc/user/project/img/protected_branches_page.pngbin7199 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_page_v12_3.pngbin0 -> 9445 bytes
-rw-r--r--doc/user/project/img/protected_tags_list.pngbin7227 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_tags_list_v12_3.pngbin0 -> 4395 bytes
-rw-r--r--doc/user/project/img/protected_tags_page.pngbin13813 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_tags_page_v12_3.pngbin0 -> 10431 bytes
-rw-r--r--doc/user/project/img/protected_tags_permissions_dropdown.pngbin7770 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_tags_permissions_dropdown_v12_3.pngbin0 -> 4517 bytes
-rw-r--r--doc/user/project/import/tfvc.md2
-rw-r--r--doc/user/project/index.md2
-rw-r--r--doc/user/project/integrations/bamboo.md4
-rw-r--r--doc/user/project/integrations/mattermost.md9
-rw-r--r--doc/user/project/integrations/mock_ci.md2
-rw-r--r--doc/user/project/integrations/prometheus.md30
-rw-r--r--doc/user/project/integrations/webhooks.md33
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md2
-rw-r--r--doc/user/project/issues/sorting_issue_lists.md4
-rw-r--r--doc/user/project/merge_requests/allow_collaboration.md3
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.pngbin24542 -> 21908 bytes
-rw-r--r--doc/user/project/merge_requests/index.md4
-rw-r--r--doc/user/project/merge_requests/license_management.md4
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md2
-rw-r--r--doc/user/project/operations/feature_flags.md31
-rw-r--r--doc/user/project/operations/tracing.md6
-rw-r--r--doc/user/project/packages/maven_repository.md3
-rw-r--r--doc/user/project/packages/npm_registry.md3
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md2
-rw-r--r--doc/user/project/pages/getting_started_part_one.md2
-rw-r--r--doc/user/project/pipelines/settings.md6
-rw-r--r--doc/user/project/protected_branches.md6
-rw-r--r--doc/user/project/protected_tags.md6
-rw-r--r--doc/user/project/repository/index.md2
-rw-r--r--doc/user/project/settings/index.md2
-rw-r--r--doc/user/reserved_names.md42
-rw-r--r--doc/user/search/advanced_search_syntax.md1
-rw-r--r--doc/workflow/git_annex.md12
-rw-r--r--doc/workflow/issue_weight.md5
-rw-r--r--doc/workflow/releases.md8
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--doc/workflow/timezone.md2
-rw-r--r--lib/api/discussions.rb2
-rw-r--r--lib/api/entities.rb15
-rw-r--r--lib/api/groups.rb1
-rw-r--r--lib/api/helpers.rb33
-rw-r--r--lib/api/helpers/groups_helpers.rb7
-rw-r--r--lib/api/helpers/internal_helpers.rb8
-rw-r--r--lib/api/helpers/notes_helpers.rb6
-rw-r--r--lib/api/internal.rb23
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/project_snippets.rb14
-rw-r--r--lib/api/settings.rb11
-rw-r--r--lib/api/validations/types/comma_separated_to_array.rb22
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb16
-rw-r--r--lib/banzai/filter/asset_proxy_filter.rb62
-rw-r--r--lib/banzai/filter/external_link_filter.rb18
-rw-r--r--lib/banzai/filter/image_link_filter.rb3
-rw-r--r--lib/banzai/filter/label_reference_filter.rb18
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb12
-rw-r--r--lib/banzai/filter/relative_link_filter.rb16
-rw-r--r--lib/banzai/filter/video_link_filter.rb15
-rw-r--r--lib/banzai/pipeline/ascii_doc_pipeline.rb5
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb3
-rw-r--r--lib/banzai/pipeline/markup_pipeline.rb5
-rw-r--r--lib/banzai/pipeline/single_line_pipeline.rb3
-rw-r--r--lib/gitlab.rb2
-rw-r--r--lib/gitlab/anonymous_session.rb39
-rw-r--r--lib/gitlab/authorized_keys.rb30
-rw-r--r--lib/gitlab/ci/pipeline/chain/limit/job_activity.rb21
-rw-r--r--lib/gitlab/database.rb4
-rw-r--r--lib/gitlab/gitaly_client.rb2
-rw-r--r--lib/gitlab/internal_post_receive/response.rb51
-rw-r--r--lib/gitlab/jira/http_client.rb66
-rw-r--r--lib/gitlab/markdown_cache.rb2
-rw-r--r--lib/gitlab/path_regex.rb2
-rw-r--r--lib/gitlab/performance_bar/with_top_level_warnings.rb19
-rw-r--r--lib/gitlab/recaptcha.rb6
-rw-r--r--lib/gitlab/redis/shared_state.rb1
-rw-r--r--lib/gitlab/repository_cache_adapter.rb53
-rw-r--r--lib/gitlab/repository_set_cache.rb67
-rw-r--r--lib/gitlab/sanitizers/exif.rb7
-rw-r--r--lib/gitlab/shell.rb86
-rw-r--r--lib/gitlab/slash_commands/command.rb1
-rw-r--r--lib/gitlab/slash_commands/issue_close.rb44
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_base.rb8
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_close.rb51
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_new.rb10
-rw-r--r--lib/gitlab/snowplow_tracker.rb35
-rw-r--r--lib/gitlab/tracking.rb44
-rw-r--r--lib/gitlab/visibility_level_checker.rb88
-rw-r--r--lib/gitlab/workhorse.rb6
-rw-r--r--lib/peek/views/active_record.rb18
-rw-r--r--lib/peek/views/detailed_view.rb45
-rw-r--r--lib/peek/views/gitaly.rb18
-rw-r--r--lib/peek/views/rugged.rb2
-rw-r--r--lib/system_check/app/authorized_keys_permission_check.rb41
-rw-r--r--lib/system_check/rake_task/app_task.rb3
-rw-r--r--lib/tasks/gitlab/uploads/sanitize.rake6
-rw-r--r--locale/gitlab.pot145
-rw-r--r--package.json9
-rw-r--r--qa/README.md11
-rw-r--r--qa/qa.rb11
-rw-r--r--qa/qa/page/admin/menu.rb10
-rw-r--r--qa/qa/page/admin/settings/component/ip_limits.rb30
-rw-r--r--qa/qa/page/admin/settings/network.rb23
-rw-r--r--qa/qa/page/component/web_ide/alert.rb27
-rw-r--r--qa/qa/page/dashboard/projects.rb2
-rw-r--r--qa/qa/page/file/show.rb2
-rw-r--r--qa/qa/page/group/settings/general.rb18
-rw-r--r--qa/qa/page/merge_request/show.rb2
-rw-r--r--qa/qa/page/profile/menu.rb2
-rw-r--r--qa/qa/page/project/commit/show.rb5
-rw-r--r--qa/qa/page/project/issue/index.rb10
-rw-r--r--qa/qa/page/project/issue/show.rb6
-rw-r--r--qa/qa/page/project/menu.rb2
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb2
-rw-r--r--qa/qa/page/project/pipeline/show.rb2
-rw-r--r--qa/qa/page/project/settings/main.rb2
-rw-r--r--qa/qa/page/project/settings/mirroring_repositories.rb2
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb2
-rw-r--r--qa/qa/page/project/show.rb2
-rw-r--r--qa/qa/page/project/web_ide/edit.rb9
-rw-r--r--qa/qa/resource/project.rb1
-rw-r--r--qa/qa/resource/repository/commit.rb66
-rw-r--r--qa/qa/resource/runner.rb36
-rw-r--r--qa/qa/resource/sandbox.rb2
-rw-r--r--qa/qa/runtime/api/client.rb21
-rw-r--r--qa/qa/runtime/env.rb2
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb2
-rw-r--r--qa/qa/service/runner.rb27
-rw-r--r--qa/qa/specs/features/api/1_manage/rate_limits_spec.rb20
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb57
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb2
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb2
-rw-r--r--rubocop/cop/rspec/be_success_matcher.rb53
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb44
-rw-r--r--spec/controllers/groups/runners_controller_spec.rb205
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb29
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb112
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb311
-rw-r--r--spec/controllers/projects/services_controller_spec.rb5
-rw-r--r--spec/controllers/sessions_controller_spec.rb108
-rw-r--r--spec/controllers/uploads_controller_spec.rb18
-rw-r--r--spec/features/admin/admin_runners_spec.rb4
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_filtering_spec.rb12
-rw-r--r--spec/features/dashboard/todos/todos_sorting_spec.rb36
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb18
-rw-r--r--spec/features/markdown/math_spec.rb6
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb17
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb1
-rw-r--r--spec/finders/members_finder_spec.rb44
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_noteable.json28
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_poll_widget.json8
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json3
-rw-r--r--spec/frontend/clusters/components/application_row_spec.js2
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js2
-rw-r--r--spec/frontend/notes/components/note_app_spec.js31
-rw-r--r--spec/frontend/tracking_spec.js46
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/file_icon_spec.js75
-rw-r--r--spec/helpers/emails_helper_spec.rb56
-rw-r--r--spec/helpers/labels_helper_spec.rb10
-rw-r--r--spec/helpers/markup_helper_spec.rb6
-rw-r--r--spec/helpers/projects_helper_spec.rb38
-rw-r--r--spec/initializers/asset_proxy_setting_spec.rb13
-rw-r--r--spec/initializers/rest-client-hostname_override_spec.rb147
-rw-r--r--spec/javascripts/environments/environment_item_spec.js5
-rw-r--r--spec/javascripts/issue_show/components/edit_actions_spec.js2
-rw-r--r--spec/javascripts/notes/mock_data.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js47
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js92
-rw-r--r--spec/lib/banzai/filter/asset_proxy_filter_spec.rb95
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb7
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb5
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb8
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb16
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb72
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb22
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb44
-rw-r--r--spec/lib/gitlab/anonymous_session_spec.rb78
-rw-r--r--spec/lib/gitlab/authorized_keys_spec.rb132
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb17
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb21
-rw-r--r--spec/lib/gitlab/internal_post_receive/response_spec.rb121
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb53
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb29
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb7
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb75
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb18
-rw-r--r--spec/lib/gitlab/shell_spec.rb485
-rw-r--r--spec/lib/gitlab/slash_commands/issue_close_spec.rb80
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb27
-rw-r--r--spec/lib/gitlab/snowplow_tracker_spec.rb45
-rw-r--r--spec/lib/gitlab/tracking_spec.rb88
-rw-r--r--spec/lib/gitlab/visibility_level_checker_spec.rb82
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb13
-rw-r--r--spec/lib/gitlab_spec.rb27
-rw-r--r--spec/lib/peek/views/detailed_view_spec.rb81
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb8
-rw-r--r--spec/lib/system_check/app/authorized_keys_permission_check_spec.rb67
-rw-r--r--spec/mailers/notify_spec.rb16
-rw-r--r--spec/models/application_setting_spec.rb65
-rw-r--r--spec/models/commit_spec.rb18
-rw-r--r--spec/models/concerns/issuable_spec.rb26
-rw-r--r--spec/models/concerns/noteable_spec.rb18
-rw-r--r--spec/models/label_spec.rb7
-rw-r--r--spec/models/list_spec.rb79
-rw-r--r--spec/models/list_user_preference_spec.rb22
-rw-r--r--spec/models/note_spec.rb1
-rw-r--r--spec/models/project_services/discord_service_spec.rb33
-rw-r--r--spec/models/remote_mirror_spec.rb7
-rw-r--r--spec/models/repository_spec.rb52
-rw-r--r--spec/models/todo_spec.rb4
-rw-r--r--spec/models/user_spec.rb43
-rw-r--r--spec/policies/issue_policy_spec.rb28
-rw-r--r--spec/policies/merge_request_policy_spec.rb89
-rw-r--r--spec/requests/api/internal_spec.rb64
-rw-r--r--spec/requests/api/project_snapshots_spec.rb7
-rw-r--r--spec/requests/api/project_snippets_spec.rb67
-rw-r--r--spec/requests/api/settings_spec.rb28
-rw-r--r--spec/requests/api/snippets_spec.rb63
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb19
-rw-r--r--spec/rubocop/cop/rspec/be_success_matcher_spec.rb63
-rw-r--r--spec/serializers/merge_request_serializer_spec.rb8
-rw-r--r--spec/services/application_settings/update_service_spec.rb33
-rw-r--r--spec/services/boards/lists/list_service_spec.rb6
-rw-r--r--spec/services/boards/lists/update_service_spec.rb89
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb16
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb10
-rw-r--r--spec/services/create_snippet_service_spec.rb13
-rw-r--r--spec/services/issues/close_service_spec.rb40
-rw-r--r--spec/services/projects/create_service_spec.rb68
-rw-r--r--spec/services/projects/forks_count_service_spec.rb14
-rw-r--r--spec/services/projects/lfs_pointers/lfs_link_service_spec.rb18
-rw-r--r--spec/services/projects/open_issues_count_service_spec.rb13
-rw-r--r--spec/services/projects/open_merge_requests_count_service_spec.rb11
-rw-r--r--spec/services/system_note_service_spec.rb7
-rw-r--r--spec/services/todo_service_spec.rb121
-rw-r--r--spec/services/update_snippet_service_spec.rb15
-rw-r--r--spec/services/users/keys_count_service_spec.rb44
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/helpers/query_recorder.rb5
-rw-r--r--spec/support/helpers/stub_configuration.rb4
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb3
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/models/concern/issuable_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/services/count_service_shared_examples.rb54
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb1
-rw-r--r--spec/views/groups/edit.html.haml_spec.rb2
-rw-r--r--vendor/licenses.csv1
-rw-r--r--yarn.lock92
566 files changed, 7457 insertions, 2664 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4c7a8c05b37..27992024265 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,8 +18,11 @@ variables:
GIT_SUBMODULE_STRATEGY: "none"
GET_SOURCES_ATTEMPTS: "3"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
+ EE_KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master-ee.json
FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/report-suite.json
BUILD_ASSETS_IMAGE: "false"
+ ES_JAVA_OPTS: "-Xms256m -Xmx256m"
+ ELASTIC_URL: "http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200"
after_script:
- date
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index 2197f916484..5622cd232ca 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -17,13 +17,23 @@ sast:
variables:
SAST_BRAKEMAN_LEVEL: 2
SAST_EXCLUDED_PATHS: qa,spec,doc
+ artifacts:
+ expire_in: 7 days
+ paths:
+ - gl-sast-report.json
dependency_scanning:
extends: .reports
dast:
- extends: .reports
+ extends:
+ - .reports
+ - .review-only
stage: qa
dependencies: ["review-deploy"]
before_script:
- export DAST_WEBSITE="$(cat review_app_url.txt)"
+ artifacts:
+ expire_in: 7 days
+ paths:
+ - gl-dast-report.json
diff --git a/.rubocop.yml b/.rubocop.yml
index 012f4890c33..a20924c21b7 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -50,7 +50,9 @@ Style/FrozenStringLiteralComment:
- 'config/**/*'
- 'danger/**/*'
- 'db/**/*'
- - 'ee/**/*'
+ - 'ee/db/**/*'
+ - 'ee/spec/**/*'
+ - 'ee/lib/tasks/**/*'
- 'lib/tasks/**/*'
- 'qa/**/*'
- 'rubocop/**/*'
@@ -91,6 +93,7 @@ Naming/FileName:
- JSON
- LDAP
- SAML
+ - SSO
- IO
- HMAC
- QA
@@ -265,3 +268,12 @@ RSpec/EnvAssignment:
- 'ee/spec/**/rails_helper.rb'
- 'spec/**/spec_helper.rb'
- 'ee/spec/**/spec_helper.rb'
+RSpec/BeSuccessMatcher:
+ Enabled: true
+ Include:
+ - 'spec/controllers/**/*'
+ - 'ee/spec/controllers/**/*'
+ - 'spec/support/shared_examples/controllers/**/*'
+ - 'ee/spec/support/shared_examples/controllers/**/*'
+ - 'spec/support/controllers/**/*'
+ - 'ee/spec/support/controllers/**/*'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ffca09a92e7..c4d238b2999 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,38 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 12.2.3
+
+### Security (22 changes)
+
+- Ensure only authorised users can create notes on Merge Requests and Issues.
+- Gitaly: ignore git redirects.
+- Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
+- Speed up regexp in namespace format by failing fast after reaching maximum namespace depth.
+- Limit the size of issuable description and comments.
+- Send TODOs for comments on commits correctly.
+- Restrict MergeRequests#test_reports to authenticated users with read-access on Builds.
+- Added image proxy to mitigate potential stealing of IP addresses.
+- Filter out old system notes for epics in notes api endpoint response.
+- Avoid exposing unaccessible repo data upon GFM post processing.
+- Fix HTML injection for label description.
+- Make sure HTML text is always escaped when replacing label/milestone references.
+- Prevent DNS rebind on JIRA service integration.
+- Use admin_group authorization in Groups::RunnersController.
+- Prevent disclosure of merge request ID via email.
+- Show cross-referenced MR-id in issues' activities only to authorized users.
+- Enforce max chars and max render time in markdown math.
+- Check permissions before responding in MergeController#pipeline_status.
+- Remove EXIF from users/personal snippet uploads.
+- Fix project import restricted visibility bypass via API.
+- Fix weak session management by clearing password reset tokens after login (username/email) are updated.
+- Fix SSRF via DNS rebinding in Kubernetes Integration.
+
+
+## 12.2.2
+
+- Unreleased due to QA failure.
+
## 12.2.1
### Fixed (3 changes)
@@ -591,6 +623,34 @@ entry.
- Removes EE differences for app/views/admin/users/show.html.haml.
+## 12.0.7
+
+### Security (22 changes)
+
+- Ensure only authorised users can create notes on Merge Requests and Issues.
+- Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
+- Queries for Upload should be scoped by model.
+- Speed up regexp in namespace format by failing fast after reaching maximum namespace depth.
+- Limit the size of issuable description and comments.
+- Send TODOs for comments on commits correctly.
+- Restrict MergeRequests#test_reports to authenticated users with read-access on Builds.
+- Added image proxy to mitigate potential stealing of IP addresses.
+- Filter out old system notes for epics in notes api endpoint response.
+- Avoid exposing unaccessible repo data upon GFM post processing.
+- Fix HTML injection for label description.
+- Make sure HTML text is always escaped when replacing label/milestone references.
+- Prevent DNS rebind on JIRA service integration.
+- Use admin_group authorization in Groups::RunnersController.
+- Prevent disclosure of merge request ID via email.
+- Show cross-referenced MR-id in issues' activities only to authorized users.
+- Enforce max chars and max render time in markdown math.
+- Check permissions before responding in MergeController#pipeline_status.
+- Remove EXIF from users/personal snippet uploads.
+- Fix project import restricted visibility bypass via API.
+- Fix weak session management by clearing password reset tokens after login (username/email) are updated.
+- Fix SSRF via DNS rebinding in Kubernetes Integration.
+
+
## 12.0.6
- No changes.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 4d5fde5bd16..91951fd8ad7 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.60.0
+1.61.0
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index 26aaba0e866..f0bb29e7638 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-1.2.0
+1.3.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 943f9cbc4ec..27f9cd322bb 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.7.1
+1.8.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 3b6825376ad..eec6dacbd48 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.8.0
+8.8.1
diff --git a/PHILOSOPHY.md b/PHILOSOPHY.md
index e966d88ef78..483063731d1 100644
--- a/PHILOSOPHY.md
+++ b/PHILOSOPHY.md
@@ -1 +1,4 @@
-This document is intended to communicate the product philosophy GitLab uses in creating GitLab Community Edition. The principles can be found in the [Product Section of the GitLab Handbook](https://about.gitlab.com/handbook/product/#product-at-gitlab). \ No newline at end of file
+To learn about the product philosophy GitLab the company uses in creating GitLab
+the product, visit our [Product Handbook page].
+
+[Product Handbook page]: https://about.gitlab.com/handbook/product/#product-at-gitlab
diff --git a/README.md b/README.md
index 054e2d02461..bfc55f28279 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,15 @@
# GitLab
-## Test coverage
-
-- [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby
-- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
-
## Canonical source
The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
+The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ee).
+
+## Free trial
+
+You can request a free trial of GitLab Ultimate [on our website](https://about.gitlab.com/free-trial/).
+
## Open source software to collaborate on code
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
@@ -103,7 +104,7 @@ For upgrading information please see our [update page](https://about.gitlab.com/
## Documentation
-All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/ce/).
+All documentation can be found on <https://docs.gitlab.com>.
## Getting help
diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js
index a68936d79e2..53867b3096b 100644
--- a/app/assets/javascripts/behaviors/markdown/render_math.js
+++ b/app/assets/javascripts/behaviors/markdown/render_math.js
@@ -1,6 +1,5 @@
-import $ from 'jquery';
-import { __ } from '~/locale';
import flash from '~/flash';
+import { s__, sprintf } from '~/locale';
// Renders math using KaTeX in any element with the
// `js-render-math` class
@@ -10,21 +9,131 @@ import flash from '~/flash';
// <code class="js-render-math"></div>
//
-// Loop over all math elements and render math
-function renderWithKaTeX(elements, katex) {
- elements.each(function katexElementsLoop() {
- const mathNode = $('<span></span>');
- const $this = $(this);
-
- const display = $this.attr('data-math-style') === 'display';
- try {
- katex.render($this.text(), mathNode.get(0), { displayMode: display, throwOnError: false });
- mathNode.insertAfter($this);
- $this.remove();
- } catch (err) {
- throw err;
+const MAX_MATH_CHARS = 1000;
+const MAX_RENDER_TIME_MS = 2000;
+
+// These messages might be used with inline errors in the future. Keep them around. For now, we will
+// display a single error message using flash().
+
+// const CHAR_LIMIT_EXCEEDED_MSG = sprintf(
+// s__(
+// 'math|The following math is too long. For performance reasons, math blocks are limited to %{maxChars} characters. Try splitting up this block, or include an image instead.',
+// ),
+// { maxChars: MAX_MATH_CHARS },
+// );
+// const RENDER_TIME_EXCEEDED_MSG = s__(
+// "math|The math in this entry is taking too long to render. Any math below this point won't be shown. Consider splitting it among multiple entries.",
+// );
+
+const RENDER_FLASH_MSG = sprintf(
+ s__(
+ 'math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead.',
+ ),
+ { maxChars: MAX_MATH_CHARS },
+);
+
+// Wait for the browser to reflow the layout. Reflowing SVG takes time.
+// This has to wrap the inner function, otherwise IE/Edge throw "invalid calling object".
+const waitForReflow = fn => {
+ window.requestAnimationFrame(fn);
+};
+
+/**
+ * Renders math blocks sequentially while protecting against DoS attacks. Math blocks have a maximum character limit of MAX_MATH_CHARS. If rendering math takes longer than MAX_RENDER_TIME_MS, all subsequent math blocks are skipped and an error message is shown.
+ */
+class SafeMathRenderer {
+ /*
+ How this works:
+
+ The performance bottleneck in rendering math is in the browser trying to reflow the generated SVG.
+ During this time, the JS is blocked and the page becomes unresponsive.
+ We want to render math blocks one by one until a certain time is exceeded, after which we stop
+ rendering subsequent math blocks, to protect against DoS. However, browsers do reflowing in an
+ asynchronous task, so we can't time it synchronously.
+
+ SafeMathRenderer essentially does the following:
+ 1. Replaces all math blocks with placeholders so that they're not mistakenly rendered twice.
+ 2. Places each placeholder element in a queue.
+ 3. Renders the element at the head of the queue and waits for reflow.
+ 4. After reflow, gets the elapsed time since step 3 and repeats step 3 until the queue is empty.
+ */
+ queue = [];
+ totalMS = 0;
+
+ constructor(elements, katex) {
+ this.elements = elements;
+ this.katex = katex;
+
+ this.renderElement = this.renderElement.bind(this);
+ this.render = this.render.bind(this);
+ }
+
+ renderElement() {
+ if (!this.queue.length) {
+ return;
}
- });
+
+ const el = this.queue.shift();
+ const text = el.textContent;
+
+ el.removeAttribute('style');
+
+ if (this.totalMS >= MAX_RENDER_TIME_MS || text.length > MAX_MATH_CHARS) {
+ if (!this.flashShown) {
+ flash(RENDER_FLASH_MSG);
+ this.flashShown = true;
+ }
+
+ // Show unrendered math code
+ const codeElement = document.createElement('pre');
+ codeElement.className = 'code';
+ codeElement.textContent = el.textContent;
+ el.parentNode.replaceChild(codeElement, el);
+
+ // Render the next math
+ this.renderElement();
+ } else {
+ this.startTime = Date.now();
+
+ try {
+ el.innerHTML = this.katex.renderToString(text, {
+ displayMode: el.getAttribute('data-math-style') === 'display',
+ throwOnError: true,
+ maxSize: 20,
+ maxExpand: 20,
+ });
+ } catch {
+ // Don't show a flash for now because it would override an existing flash message
+ el.textContent = s__('math|There was an error rendering this math block');
+ // el.style.color = '#d00';
+ el.className = 'katex-error';
+ }
+
+ // Give the browser time to reflow the svg
+ waitForReflow(() => {
+ const deltaTime = Date.now() - this.startTime;
+ this.totalMS += deltaTime;
+
+ this.renderElement();
+ });
+ }
+ }
+
+ render() {
+ // Replace math blocks with a placeholder so they aren't rendered twice
+ this.elements.forEach(el => {
+ const placeholder = document.createElement('span');
+ placeholder.style.display = 'none';
+ placeholder.setAttribute('data-math-style', el.getAttribute('data-math-style'));
+ placeholder.textContent = el.textContent;
+ el.parentNode.replaceChild(placeholder, el);
+ this.queue.push(placeholder);
+ });
+
+ // If we wait for the browser thread to settle down a bit, math rendering becomes 5-10x faster
+ // and less prone to timeouts.
+ setTimeout(this.renderElement, 400);
+ }
}
export default function renderMath($els) {
@@ -34,7 +143,8 @@ export default function renderMath($els) {
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
])
.then(([katex]) => {
- renderWithKaTeX($els, katex);
+ const renderer = new SafeMathRenderer($els.get(), katex);
+ renderer.render();
})
- .catch(() => flash(__('An error occurred while rendering KaTeX')));
+ .catch(() => {});
}
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index ada5a49e246..772f16cab4e 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -55,7 +55,7 @@ export default class ClusterStore {
...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'),
version: null,
- chartRepo: 'https://gitlab.com/charts/gitlab-runner',
+ chartRepo: 'https://gitlab.com/gitlab-org/charts/gitlab-runner',
updateAvailable: null,
updateSuccessful: false,
updateFailed: false,
diff --git a/app/assets/javascripts/droplab/drop_lab.js b/app/assets/javascripts/droplab/drop_lab.js
index 1339e28d8b8..33c05404493 100644
--- a/app/assets/javascripts/droplab/drop_lab.js
+++ b/app/assets/javascripts/droplab/drop_lab.js
@@ -60,7 +60,7 @@ class DropLab {
addEvents() {
this.eventWrapper.documentClicked = this.documentClicked.bind(this);
- document.addEventListener('click', this.eventWrapper.documentClicked);
+ document.addEventListener('mousedown', this.eventWrapper.documentClicked);
}
documentClicked(e) {
@@ -74,7 +74,7 @@ class DropLab {
}
removeEvents() {
- document.removeEventListener('click', this.eventWrapper.documentClicked);
+ document.removeEventListener('mousedown', this.eventWrapper.documentClicked);
}
changeHookList(trigger, list, plugins, config) {
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 95e1e8af9b3..1d4a6e64f9d 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -111,12 +111,7 @@ export default {
* @returns {Boolean|Undefined}
*/
canShowDate() {
- return (
- this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable !== undefined
- );
+ return this.model && this.model.last_deployment && this.model.last_deployment.deployed_at;
},
/**
@@ -124,14 +119,9 @@ export default {
*
* @returns {String}
*/
- createdDate() {
- if (
- this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable.created_at
- ) {
- return timeagoInstance.format(this.model.last_deployment.deployable.created_at);
+ deployedDate() {
+ if (this.canShowDate) {
+ return timeagoInstance.format(this.model.last_deployment.deployed_at);
}
return '';
},
@@ -547,7 +537,7 @@ export default {
<div v-if="!model.isFolder" class="table-section section-10" role="gridcell">
<div role="rowheader" class="table-mobile-header">{{ s__('Environments|Updated') }}</div>
<span v-if="canShowDate" class="environment-created-date-timeago table-mobile-content">
- {{ createdDate }}
+ {{ deployedDate }}
</span>
</div>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index 8b356ee6e97..549324831e9 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -69,7 +69,11 @@ export default {
:disabled="currentBranch && !currentBranch.can_push"
:title="$options.currentBranchPermissionsTooltip"
>
- <span class="ide-radio-label" v-html="commitToCurrentBranchText"> </span>
+ <span
+ class="ide-radio-label"
+ data-qa-selector="commit_to_current_branch_radio"
+ v-html="commitToCurrentBranchText"
+ ></span>
</radio-group>
<radio-group
:value="$options.commitToNewBranch"
diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue
index 22113692968..500f6737839 100644
--- a/app/assets/javascripts/ide/components/error_message.vue
+++ b/app/assets/javascripts/ide/components/error_message.vue
@@ -44,7 +44,7 @@ export default {
<template>
<div class="flash-container flash-container-page" @click="clickFlash">
- <div class="flash-alert">
+ <div class="flash-alert" data-qa-selector="flash_alert">
<span v-html="message.text"> </span>
<button
v-if="message.action"
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index 2e6bd85feec..200391282e7 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -89,7 +89,7 @@ export default {
</script>
<template>
- <div class="multi-file-commit-panel ide-right-sidebar">
+ <div class="multi-file-commit-panel ide-right-sidebar" data-qa-selector="ide_right_sidebar">
<resizable-panel
v-show="isOpen"
:collapsible="false"
@@ -120,6 +120,7 @@ export default {
}"
data-container="body"
data-placement="left"
+ :data-qa-selector="`${tab.title.toLowerCase()}_tab_button`"
class="ide-sidebar-link is-right"
type="button"
@click="clickTab($event, tab)"
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 9ca38d6bbfa..88975c2cc73 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -300,9 +300,9 @@ export default {
this.closeRecaptcha();
},
- deleteIssuable() {
+ deleteIssuable(payload) {
this.service
- .deleteIssuable()
+ .deleteIssuable(payload)
.then(res => res.data)
.then(data => {
// Stop the poll so we don't get 404's with the issuable not existing
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
index eb51a074f84..ce867f16acf 100644
--- a/app/assets/javascripts/issue_show/components/edit_actions.vue
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -55,7 +55,7 @@ export default {
if (window.confirm(confirmMessage)) {
this.deleteLoading = true;
- eventHub.$emit('delete.issuable');
+ eventHub.$emit('delete.issuable', { destroy_confirm: true });
}
},
},
diff --git a/app/assets/javascripts/issue_show/services/index.js b/app/assets/javascripts/issue_show/services/index.js
index 9546eb22c27..3c8334bee50 100644
--- a/app/assets/javascripts/issue_show/services/index.js
+++ b/app/assets/javascripts/issue_show/services/index.js
@@ -10,8 +10,8 @@ export default class Service {
return axios.get(this.realtimeEndpoint);
}
- deleteIssuable() {
- return axios.delete(this.endpoint);
+ deleteIssuable(payload) {
+ return axios.delete(this.endpoint, { params: payload });
}
updateIssuable(data) {
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 39f2097c174..0ddf40b0405 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -35,6 +35,7 @@ import initPerformanceBar from './performance_bar';
import initSearchAutocomplete from './search_autocomplete';
import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
+import { initUserTracking } from './tracking';
import { __ } from './locale';
import 'ee_else_ce/main_ee';
@@ -94,6 +95,7 @@ function deferredInitialisation() {
initLogoAnimation();
initUsagePingConsent();
initUserPopovers();
+ initUserTracking();
if (document.querySelector('.search')) initSearchAutocomplete();
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index a0695f9e191..16a0fb3f33a 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -75,9 +75,9 @@ export default {
},
allDiscussions() {
if (this.isLoading) {
- const totalNotes = parseInt(this.notesData.totalNotes, 10) || 0;
+ const prerenderedNotesCount = parseInt(this.notesData.prerenderedNotesCount, 10) || 0;
- return new Array(totalNotes).fill({
+ return new Array(prerenderedNotesCount).fill({
isSkeletonNote: true,
});
}
diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js
index a852f937eec..03281b5ef49 100644
--- a/app/assets/javascripts/tracking.js
+++ b/app/assets/javascripts/tracking.js
@@ -1,5 +1,23 @@
import $ from 'jquery';
+const DEFAULT_SNOWPLOW_OPTIONS = {
+ namespace: 'gl',
+ hostname: window.location.hostname,
+ cookieDomain: window.location.hostname,
+ appId: '',
+ userFingerprint: false,
+ respectDoNotTrack: true,
+ forceSecureTracker: true,
+ eventMethod: 'post',
+ contexts: { webPage: true },
+ // Page tracking tracks a single event when the page loads.
+ pageTrackingEnabled: false,
+ // Activity tracking tracks when a user is still interacting with the page.
+ // Events like scrolling and mouse movements are used to determine if the
+ // user has the tab active and is still actively engaging.
+ activityTrackingEnabled: false,
+};
+
const extractData = (el, opts = {}) => {
const { trackEvent, trackLabel = '', trackProperty = '' } = el.dataset;
let trackValue = el.dataset.trackValue || el.value || '';
@@ -71,3 +89,13 @@ export default class Tracking {
};
}
}
+
+export function initUserTracking() {
+ if (!Tracking.enabled()) return;
+
+ const opts = Object.assign({}, DEFAULT_SNOWPLOW_OPTIONS, window.snowplowOptions);
+ window.snowplow('newTracker', opts.namespace, opts.hostname, opts);
+
+ if (opts.activityTrackingEnabled) window.snowplow('enableActivityTracking', 30, 30);
+ if (opts.pageTrackingEnabled) window.snowplow('trackPageView'); // must be after enableActivityTracking
+}
diff --git a/app/assets/stylesheets/csslab.scss b/app/assets/stylesheets/csslab.scss
deleted file mode 100644
index 87c59cd42c0..00000000000
--- a/app/assets/stylesheets/csslab.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import "@gitlab/csslab/dist/css/csslab-slim";
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 821e6691fe4..69ef116043a 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -245,27 +245,3 @@ label {
.input-group-text {
max-height: $input-height;
}
-
-.gl-form-checkbox {
- align-items: baseline;
- margin-right: 1rem;
- margin-bottom: 0.25rem;
-
- .form-check-input {
- margin-right: 0;
- }
-
- .form-check-label {
- padding-left: $gl-padding-8;
- }
-
- &.form-check-inline .form-check-input {
- align-self: flex-start;
- height: 1.5 * $gl-font-size;
- }
-
- .form-check-input:disabled,
- .form-check-input:disabled ~ .form-check-label {
- cursor: not-allowed;
- }
-}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index c201605e83d..33caac4d725 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -2,7 +2,7 @@
* Apply Markup (Markdown/AsciiDoc) typography
*
*/
-.md:not(.use-csslab) {
+.md {
color: $gl-text-color;
word-wrap: break-word;
@@ -384,8 +384,18 @@
@extend .fa-exclamation-circle;
}
}
-}
+ .prometheus-graph-embed {
+ h3.popover-header {
+ /** Override <h3> .popover-header
+ * as embed metrics do not follow the same
+ * style as default md <h3> (which are deeply nested)
+ */
+ margin: 0;
+ font-size: $gl-font-size-small;
+ }
+ }
+}
/**
* Headers
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 9871771542d..7a3fd2adfbb 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -529,7 +529,7 @@ $award-emoji-width-xs: 90%;
*/
$search-input-border-color: rgba($blue-400, 0.8);
$search-input-width: 200px;
-$search-input-active-width: 320px;
+$search-input-xl-width: 320px;
$location-icon-color: #e7e9ed;
/*
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 58e46cfb70f..74380ec995a 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -48,6 +48,10 @@ input[type='checkbox']:hover {
background-color ease-in-out $default-transition-duration,
width ease-in-out $default-transition-duration;
+ @include media-breakpoint-up(xl) {
+ width: $search-input-xl-width;
+ }
+
&:hover {
box-shadow: none;
}
@@ -116,7 +120,7 @@ input[type='checkbox']:hover {
overflow: auto;
@include media-breakpoint-up(xl) {
- width: $search-input-active-width;
+ width: $search-input-xl-width;
}
}
@@ -131,10 +135,6 @@ input[type='checkbox']:hover {
border-color: $blue-300;
box-shadow: none;
- @include media-breakpoint-up(xl) {
- width: $search-input-active-width;
- }
-
.search-input-wrap {
.search-icon,
.clear-icon {
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 7b64c67ae34..ece0ac04baf 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -72,12 +72,7 @@
@include transition(opacity);
.todo-title {
- display: flex;
-
> .title-item {
- flex: 0 0 auto;
- margin: 0 2px;
-
&:first-child {
margin-left: 0;
}
@@ -105,8 +100,12 @@
font-size: 14px;
}
- .action-name {
- font-weight: $gl-font-weight-normal;
+ .todo-label,
+ .todo-project {
+ a {
+ color: $blue-600;
+ font-weight: $gl-font-weight-normal;
+ }
}
.todo-body {
@@ -170,7 +169,7 @@
}
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
.todo {
.avatar {
display: none;
@@ -179,7 +178,6 @@
.todo-item {
.todo-title {
- flex-flow: row wrap;
margin-bottom: 10px;
.todo-label {
diff --git a/app/controllers/boards/lists_controller.rb b/app/controllers/boards/lists_controller.rb
index ccd02144671..08b4748d7e1 100644
--- a/app/controllers/boards/lists_controller.rb
+++ b/app/controllers/boards/lists_controller.rb
@@ -4,7 +4,7 @@ module Boards
class ListsController < Boards::ApplicationController
include BoardsResponses
- before_action :authorize_admin_list, only: [:create, :update, :destroy, :generate]
+ before_action :authorize_admin_list, only: [:create, :destroy, :generate]
before_action :authorize_read_list, only: [:index]
skip_before_action :authenticate_user!, only: [:index]
@@ -15,7 +15,7 @@ module Boards
end
def create
- list = Boards::Lists::CreateService.new(board.parent, current_user, list_params).execute(board)
+ list = Boards::Lists::CreateService.new(board.parent, current_user, create_list_params).execute(board)
if list.valid?
render json: serialize_as_json(list)
@@ -26,12 +26,13 @@ module Boards
def update
list = board.lists.movable.find(params[:id])
- service = Boards::Lists::MoveService.new(board_parent, current_user, move_params)
+ service = Boards::Lists::UpdateService.new(board_parent, current_user, update_list_params)
+ result = service.execute(list)
- if service.execute(list)
+ if result[:status] == :success
head :ok
else
- head :unprocessable_entity
+ head result[:http_status]
end
end
@@ -50,7 +51,8 @@ module Boards
service = Boards::Lists::GenerateService.new(board_parent, current_user)
if service.execute(board)
- render json: serialize_as_json(board.lists.movable)
+ lists = board.lists.movable.preload_associations(current_user)
+ render json: serialize_as_json(lists)
else
head :unprocessable_entity
end
@@ -62,12 +64,12 @@ module Boards
%i[label_id]
end
- def list_params
+ def create_list_params
params.require(:list).permit(list_creation_attrs)
end
- def move_params
- params.require(:list).permit(:position)
+ def update_list_params
+ params.require(:list).permit(:collapsed, :position)
end
def serialize_as_json(resource)
@@ -78,7 +80,9 @@ module Boards
{
only: [:id, :list_type, :position],
methods: [:title],
- label: true
+ label: true,
+ collapsed: true,
+ current_user: current_user
}
end
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index b86e4451a7e..e537c11096c 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -6,6 +6,7 @@ module IssuableActions
included do
before_action :authorize_destroy_issuable!, only: :destroy
+ before_action :check_destroy_confirmation!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
before_action only: :show do
push_frontend_feature_flag(:scoped_labels, default_enabled: true)
@@ -91,6 +92,33 @@ module IssuableActions
end
end
+ def check_destroy_confirmation!
+ return true if params[:destroy_confirm]
+
+ error_message = "Destroy confirmation not provided for #{issuable.human_class_name}"
+ exception = RuntimeError.new(error_message)
+ Gitlab::Sentry.track_acceptable_exception(
+ exception,
+ extra: {
+ project_path: issuable.project.full_path,
+ issuable_type: issuable.class.name,
+ issuable_id: issuable.id
+ }
+ )
+
+ index_path = polymorphic_path([parent, issuable.class])
+
+ respond_to do |format|
+ format.html do
+ flash[:notice] = error_message
+ redirect_to index_path
+ end
+ format.json do
+ render json: { errors: error_message }, status: :unprocessable_entity
+ end
+ end
+ end
+
def bulk_update
result = Issuable::BulkUpdateService.new(current_user, bulk_update_params).execute(resource_name)
quantity = result[:count]
@@ -110,7 +138,7 @@ module IssuableActions
end
notes = prepare_notes_for_rendering(notes)
- notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes = notes.select { |n| n.visible_for?(current_user) }
discussions = Discussion.build_collection(notes, issuable)
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 4b7899d469b..fbae4c53c31 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -29,7 +29,7 @@ module NotesActions
end
notes = prepare_notes_for_rendering(notes)
- notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes = notes.select { |n| n.visible_for?(current_user) }
notes_json[:notes] =
if use_note_serializer?
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index f5d35379e10..60a68cec3c3 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -127,4 +127,8 @@ module UploadsActions
def model
strong_memoize(:model) { find_model }
end
+
+ def workhorse_authorize_request?
+ action_name == 'authorize'
+ end
end
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index f8e32451b02..af2b2cbd1fd 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -3,7 +3,7 @@
class Groups::RunnersController < Groups::ApplicationController
# Proper policies should be implemented per
# https://gitlab.com/gitlab-org/gitlab-ce/issues/45894
- before_action :authorize_admin_pipeline!
+ before_action :authorize_admin_group!
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
@@ -50,10 +50,6 @@ class Groups::RunnersController < Groups::ApplicationController
@runner ||= @group.runners.find(params[:id])
end
- def authorize_admin_pipeline!
- return render_404 unless can?(current_user, :admin_pipeline, group)
- end
-
def runner_params
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index f4cc0a5851b..ea1dd7d19d5 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -12,6 +12,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
skip_before_action :merge_request, only: [:index, :bulk_update]
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
+ before_action :authorize_test_reports!, only: [:test_reports]
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
@@ -46,6 +47,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request
@commits_count = @merge_request.commits_count
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
+ @current_user_data = UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json
set_pipeline_variables
@@ -188,7 +190,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def pipeline_status
render json: PipelineSerializer
.new(project: @project, current_user: @current_user)
- .represent_status(@merge_request.head_pipeline)
+ .represent_status(head_pipeline)
end
def ci_environments_status
@@ -238,6 +240,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
private
+ def head_pipeline
+ strong_memoize(:head_pipeline) do
+ pipeline = @merge_request.head_pipeline
+ pipeline if can?(current_user, :read_pipeline, pipeline)
+ end
+ end
+
def ci_environments_status_on_merge_result?
params[:environment_target] == 'merge_commit'
end
@@ -336,4 +345,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render json: { status_reason: 'Unknown error' }, status: :internal_server_error
end
end
+
+ def authorize_test_reports!
+ # MergeRequest#actual_head_pipeline is the pipeline accessed in MergeRequest#compare_reports.
+ return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline)
+ end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 1880bead3ee..7b682cc0cc5 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -21,10 +21,13 @@ class SessionsController < Devise::SessionsController
prepend_before_action :ensure_password_authentication_enabled!, if: -> { action_name == 'create' && password_based_login? }
before_action :auto_sign_in_with_provider, only: [:new]
+ before_action :store_unauthenticated_sessions, only: [:new]
+ before_action :save_failed_login, if: :action_new_and_failed_login?
before_action :load_recaptcha
- after_action :log_failed_login, if: -> { action_name == 'new' && failed_login? }
- helper_method :captcha_enabled?
+ after_action :log_failed_login, if: :action_new_and_failed_login?
+
+ helper_method :captcha_enabled?, :captcha_on_login_required?
# protect_from_forgery is already prepended in ApplicationController but
# authenticate_with_two_factor which signs in the user is prepended before
@@ -38,6 +41,7 @@ class SessionsController < Devise::SessionsController
protect_from_forgery with: :exception, prepend: true
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
+ MAX_FAILED_LOGIN_ATTEMPTS = 5
def new
set_minimum_password_length
@@ -81,10 +85,14 @@ class SessionsController < Devise::SessionsController
request.headers[CAPTCHA_HEADER] && Gitlab::Recaptcha.enabled?
end
+ def captcha_on_login_required?
+ Gitlab::Recaptcha.enabled_on_login? && unverified_anonymous_user?
+ end
+
# From https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise#devisepasswordscontroller
def check_captcha
return unless user_params[:password].present?
- return unless captcha_enabled?
+ return unless captcha_enabled? || captcha_on_login_required?
return unless Gitlab::Recaptcha.load_configurations!
if verify_recaptcha
@@ -126,10 +134,28 @@ class SessionsController < Devise::SessionsController
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
end
+ def action_new_and_failed_login?
+ action_name == 'new' && failed_login?
+ end
+
+ def save_failed_login
+ session[:failed_login_attempts] ||= 0
+ session[:failed_login_attempts] += 1
+ end
+
def failed_login?
(options = request.env["warden.options"]) && options[:action] == "unauthenticated"
end
+ # storing sessions per IP lets us check if there are associated multiple
+ # anonymous sessions with one IP and prevent situations when there are
+ # multiple attempts of logging in
+ def store_unauthenticated_sessions
+ return if current_user
+
+ Gitlab::AnonymousSession.new(request.remote_ip, session_id: request.session.id).store_session_id_per_ip
+ end
+
# Handle an "initial setup" state, where there's only one user, it's an admin,
# and they require a password change.
# rubocop: disable CodeReuse/ActiveRecord
@@ -240,6 +266,18 @@ class SessionsController < Devise::SessionsController
@ldap_servers ||= Gitlab::Auth::LDAP::Config.available_servers
end
+ def unverified_anonymous_user?
+ exceeded_failed_login_attempts? || exceeded_anonymous_sessions?
+ end
+
+ def exceeded_failed_login_attempts?
+ session.fetch(:failed_login_attempts, 0) > MAX_FAILED_LOGIN_ATTEMPTS
+ end
+
+ def exceeded_anonymous_sessions?
+ Gitlab::AnonymousSession.new(request.remote_ip).stored_sessions >= MAX_FAILED_LOGIN_ATTEMPTS
+ end
+
def authentication_method
if user_params[:otp_attempt]
"two-factor"
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 94bd18f70d4..2adfeab182e 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -2,6 +2,7 @@
class UploadsController < ApplicationController
include UploadsActions
+ include WorkhorseRequest
UnknownUploadModelError = Class.new(StandardError)
@@ -21,7 +22,8 @@ class UploadsController < ApplicationController
before_action :upload_mount_satisfied?
before_action :find_model
before_action :authorize_access!, only: [:show]
- before_action :authorize_create_access!, only: [:create]
+ before_action :authorize_create_access!, only: [:create, :authorize]
+ before_action :verify_workhorse_api!, only: [:authorize]
def uploader_class
PersonalFileUploader
@@ -72,7 +74,7 @@ class UploadsController < ApplicationController
end
def render_unauthorized
- if current_user
+ if current_user || workhorse_authorize_request?
render_404
else
authenticate_user!
diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb
index 4155b6af8da..5e0dbbfca2e 100644
--- a/app/finders/group_projects_finder.rb
+++ b/app/finders/group_projects_finder.rb
@@ -23,8 +23,12 @@ class GroupProjectsFinder < ProjectsFinder
attr_reader :group, :options
def initialize(group:, params: {}, options: {}, current_user: nil, project_ids_relation: nil)
- super(params: params, current_user: current_user, project_ids_relation: project_ids_relation)
- @group = group
+ super(
+ params: params,
+ current_user: current_user,
+ project_ids_relation: project_ids_relation
+ )
+ @group = group
@options = options
end
@@ -84,15 +88,13 @@ class GroupProjectsFinder < ProjectsFinder
options.fetch(:include_subgroups, false)
end
- # rubocop: disable CodeReuse/ActiveRecord
def owned_projects
if include_subgroups?
- Project.where(namespace_id: group.self_and_descendants.select(:id))
+ Project.for_group_and_its_subgroups(group)
else
group.projects
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def shared_projects
group.shared_projects
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
index f730b015c0a..e8c7f9622a9 100644
--- a/app/finders/members_finder.rb
+++ b/app/finders/members_finder.rb
@@ -60,15 +60,32 @@ class MembersFinder
# We're interested in a list of members without duplicates by user_id.
# We prefer project members over group members, project members should go first.
<<~SQL
- SELECT DISTINCT ON (user_id, invite_email) member_union.*
- FROM (#{union.to_sql}) AS member_union
- ORDER BY user_id,
- invite_email,
- CASE
- WHEN type = 'ProjectMember' THEN 1
- WHEN type = 'GroupMember' THEN 2
- ELSE 3
- END
+ SELECT DISTINCT ON (user_id, invite_email) #{member_columns}
+ FROM (#{union.to_sql}) AS #{member_union_table}
+ LEFT JOIN users on users.id = member_union.user_id
+ LEFT JOIN project_authorizations on project_authorizations.user_id = users.id
+ AND
+ project_authorizations.project_id = #{project.id}
+ ORDER BY user_id,
+ invite_email,
+ CASE
+ WHEN type = 'ProjectMember' THEN 1
+ WHEN type = 'GroupMember' THEN 2
+ ELSE 3
+ END
SQL
end
+
+ def member_union_table
+ 'member_union'
+ end
+
+ def member_columns
+ Member.column_names.map do |column_name|
+ # fallback to members.access_level when project_authorizations.access_level is missing
+ next "COALESCE(#{ProjectAuthorization.table_name}.access_level, #{member_union_table}.access_level) access_level" if column_name == 'access_level'
+
+ "#{member_union_table}.#{column_name}"
+ end.join(',')
+ end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 0ab19f1d2d2..84021d0da56 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -164,6 +164,10 @@ module ApplicationSettingsHelper
:allow_local_requests_from_system_hooks,
:dns_rebinding_protection_enabled,
:archive_builds_in_human_readable,
+ :asset_proxy_enabled,
+ :asset_proxy_secret_key,
+ :asset_proxy_url,
+ :asset_proxy_whitelist,
:authorized_keys_enabled,
:auto_devops_enabled,
:auto_devops_domain,
@@ -231,6 +235,7 @@ module ApplicationSettingsHelper
:recaptcha_enabled,
:recaptcha_private_key,
:recaptcha_site_key,
+ :login_recaptcha_protection_enabled,
:receive_max_input_size,
:repository_checks_enabled,
:repository_storages,
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 36122d3a22a..23596769738 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -90,6 +90,8 @@ module EmailsHelper
when MergeRequest
merge_request = MergeRequest.find(closed_via[:id]).present
+ return "" unless Ability.allowed?(@recipient, :read_merge_request, merge_request)
+
case format
when :html
merge_request_link = link_to(merge_request.to_reference, merge_request.web_url)
@@ -102,6 +104,8 @@ module EmailsHelper
# Technically speaking this should be Commit but per
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15610#note_163812339
# we can't deserialize Commit without custom serializer for ActiveJob
+ return "" unless Ability.allowed?(@recipient, :download_code, @project)
+
_("via %{closed_via}") % { closed_via: closed_via }
else
""
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 2ed016beea4..c5a3507637e 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -71,7 +71,7 @@ module LabelsHelper
end
def label_tooltip_title(label)
- label.description
+ Sanitize.clean(label.description)
end
def suggested_colors
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 2e31a5e2ed4..4e88b379e16 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module NotesHelper
+ MAX_PRERENDERED_NOTES = 10
+
def note_target_fields(note)
if note.noteable
hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
@@ -169,7 +171,7 @@ module NotesHelper
closePath: close_issuable_path(issuable),
reopenPath: reopen_issuable_path(issuable),
notesPath: notes_url,
- totalNotes: issuable.discussions.length,
+ prerenderedNotesCount: issuable.capped_notes_count(MAX_PRERENDERED_NOTES),
lastFetchedAt: Time.now.to_i
}
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 33bf2d57fae..14f947a03a3 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -448,7 +448,7 @@ module ProjectsHelper
def git_user_email
if current_user
- current_user.email
+ current_user.commit_email
else
"your@email.com"
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 38142bc68cb..f5333bb332e 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -33,7 +33,23 @@ module TodosHelper
todo.target_reference
end
- link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
+ link_to text, todo_target_path(todo)
+ end
+
+ def todo_target_title(todo)
+ if todo.target
+ "\"#{todo.target.title}\""
+ else
+ ""
+ end
+ end
+
+ def todo_parent_path(todo)
+ if todo.parent.is_a?(Group)
+ link_to todo.parent.name, group_path(todo.parent)
+ else
+ link_to_project(todo.project)
+ end
end
def todo_target_type_name(todo)
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index f3a3203f7ad..47d15836da0 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -11,7 +11,7 @@ module Emails
def issue_due_email(recipient_id, issue_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
- mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
+ mail_answer_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
end
def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
@@ -34,6 +34,8 @@ module Emails
setup_issue_mail(issue_id, recipient_id, closed_via: closed_via)
@updated_by = User.find(updated_by_user_id)
+ @recipient = User.find(recipient_id)
+
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 2a99c6e5c59..d6caf092ed0 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -18,12 +18,19 @@ class ApplicationSetting < ApplicationRecord
# fix a lot of tests using allow_any_instance_of
include ApplicationSettingImplementation
+ attr_encrypted :asset_proxy_secret_key,
+ mode: :per_attribute_iv,
+ insecure_mode: true,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc'
+
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
ignore_column :koding_url
ignore_column :koding_enabled
@@ -75,11 +82,11 @@ class ApplicationSetting < ApplicationRecord
validates :recaptcha_site_key,
presence: true,
- if: :recaptcha_enabled
+ if: :recaptcha_or_login_protection_enabled
validates :recaptcha_private_key,
presence: true,
- if: :recaptcha_enabled
+ if: :recaptcha_or_login_protection_enabled
validates :akismet_api_key,
presence: true,
@@ -192,6 +199,17 @@ class ApplicationSetting < ApplicationRecord
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 }
+ validates :asset_proxy_url,
+ presence: true,
+ allow_blank: false,
+ url: true,
+ if: :asset_proxy_enabled?
+
+ validates :asset_proxy_secret_key,
+ presence: true,
+ allow_blank: false,
+ if: :asset_proxy_enabled?
+
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
@@ -292,4 +310,8 @@ class ApplicationSetting < ApplicationRecord
def self.cache_backend
Gitlab::ThreadMemoryCache.cache_backend
end
+
+ def recaptcha_or_login_protection_enabled
+ recaptcha_enabled || login_recaptcha_protection_enabled
+ end
end
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 55ac1e129cf..f402c0e2775 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -23,8 +23,9 @@ module ApplicationSettingImplementation
akismet_enabled: false,
allow_local_requests_from_web_hooks_and_services: false,
allow_local_requests_from_system_hooks: true,
- dns_rebinding_protection_enabled: true,
+ asset_proxy_enabled: false,
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
+ commit_email_hostname: default_commit_email_hostname,
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
@@ -33,7 +34,9 @@ module ApplicationSettingImplementation
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
disabled_oauth_sign_in_sources: [],
+ dns_rebinding_protection_enabled: true,
domain_whitelist: Settings.gitlab['domain_whitelist'],
dsa_key_restriction: 0,
ecdsa_key_restriction: 0,
@@ -52,9 +55,11 @@ module ApplicationSettingImplementation
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Settings.gitlab['import_sources'],
+ local_markdown_version: 0,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
mirror_available: true,
+ outbound_local_requests_whitelist: [],
password_authentication_enabled_for_git: true,
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
performance_bar_allowed_group_id: nil,
@@ -63,7 +68,10 @@ module ApplicationSettingImplementation
plantuml_url: nil,
polling_interval_multiplier: 1,
project_export_enabled: true,
+ protected_ci_variables: false,
+ raw_blob_request_limit: 300,
recaptcha_enabled: false,
+ login_recaptcha_protection_enabled: false,
repository_checks_enabled: true,
repository_storages: ['default'],
require_two_factor_authentication: false,
@@ -95,16 +103,10 @@ module ApplicationSettingImplementation
user_default_internal_regex: nil,
user_show_add_ssh_key_message: true,
usage_stats_set_by_user_id: nil,
- diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
- commit_email_hostname: default_commit_email_hostname,
snowplow_collector_hostname: nil,
snowplow_cookie_domain: nil,
snowplow_enabled: false,
- snowplow_site_id: nil,
- protected_ci_variables: false,
- local_markdown_version: 0,
- outbound_local_requests_whitelist: [],
- raw_blob_request_limit: 300
+ snowplow_site_id: nil
}
end
@@ -198,6 +200,15 @@ module ApplicationSettingImplementation
end
end
+ def asset_proxy_whitelist=(values)
+ values = domain_strings_to_array(values) if values.is_a?(String)
+
+ # make sure we always whitelist the running host
+ values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.host)
+
+ self[:asset_proxy_whitelist] = values
+ end
+
def repository_storages
Array(read_attribute(:repository_storages))
end
@@ -306,6 +317,7 @@ module ApplicationSettingImplementation
values
.split(DOMAIN_LIST_SEPARATOR)
+ .map(&:strip)
.reject(&:empty?)
.uniq
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index e132cb045e2..b4497d8af09 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -87,6 +87,8 @@ module Ci
scope :expired, -> (limit) { where('expire_at < ?', Time.now).limit(limit) }
+ scope :scoped_project, -> { where('ci_job_artifacts.project_id = projects.id') }
+
delegate :filename, :exists?, :open, to: :file
enum file_type: {
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 0a943a33bbb..64e372878e6 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -203,6 +203,7 @@ module Ci
scope :for_sha, -> (sha) { where(sha: sha) }
scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) }
scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) }
+ scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
scope :triggered_by_merge_request, -> (merge_request) do
where(source: :merge_request_event, merge_request: merge_request)
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 6533b7a186e..329250255fd 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.7.0'.freeze
+ VERSION = '0.8.0'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 0889ce7e287..1470b50f396 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -35,6 +35,7 @@ class Commit
MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
+ EXACT_COMMIT_SHA_PATTERN = /\A#{COMMIT_SHA_PATTERN}\z/.freeze
# Used by GFM to match and present link extensions on node texts and hrefs.
LINK_EXTENSION_PATTERN = /(patch)/.freeze
@@ -90,7 +91,7 @@ class Commit
end
def valid_hash?(key)
- !!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key)
+ !!(EXACT_COMMIT_SHA_PATTERN =~ key)
end
def lazy(project, oid)
@@ -139,6 +140,10 @@ class Commit
'@'
end
+ def self.reference_valid?(reference)
+ !!(reference =~ EXACT_COMMIT_SHA_PATTERN)
+ end
+
# Pattern used to extract commit references from text
#
# This pattern supports cross-project references.
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index db46d7afbb9..eefe9f00836 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -73,6 +73,7 @@ module Issuable
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
+ validates :description, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }, allow_blank: true
validate :milestone_is_valid
scope :authored, ->(user) { where(author_id: user) }
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 4b428b0af83..6a44bc7c401 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -73,6 +73,10 @@ module Noteable
.discussions(self)
end
+ def capped_notes_count(max)
+ notes.limit(max).count
+ end
+
def grouped_diff_discussions(*args)
# Doesn't use `discussion_notes`, because this may include commit diff notes
# besides MR diff notes, that we do not want to display on the MR Changes tab.
diff --git a/app/models/group.rb b/app/models/group.rb
index 6c868b1d1f0..61a4802a6ee 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -365,6 +365,8 @@ class Group < Namespace
end
def max_member_access_for_user(user)
+ return GroupMember::NO_ACCESS unless user
+
return GroupMember::OWNER if user.admin?
members_with_parents
diff --git a/app/models/label.rb b/app/models/label.rb
index d9455b36242..dc9f0a3d1a9 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -199,7 +199,11 @@ class Label < ApplicationRecord
end
def title=(value)
- write_attribute(:title, sanitize_title(value)) if value.present?
+ write_attribute(:title, sanitize_value(value)) if value.present?
+ end
+
+ def description=(value)
+ write_attribute(:description, sanitize_value(value)) if value.present?
end
##
@@ -260,7 +264,7 @@ class Label < ApplicationRecord
end
end
- def sanitize_title(value)
+ def sanitize_value(value)
CGI.unescapeHTML(Sanitize.clean(value.to_s))
end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 79a376ff0fd..40695a97d97 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -2,6 +2,7 @@
class LfsObject < ApplicationRecord
include AfterCommitQueue
+ include EachBatch
include ObjectStorage::BackgroundMove
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/models/list.rb b/app/models/list.rb
index ccadd39bda2..ae7085f05a7 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -1,9 +1,11 @@
# frozen_string_literal: true
class List < ApplicationRecord
+ include Importable
+
belongs_to :board
belongs_to :label
- include Importable
+ has_many :list_user_preferences
enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4 }
@@ -16,9 +18,24 @@ class List < ApplicationRecord
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
- scope :preload_associations, -> { preload(:board, :label) }
+
+ scope :preload_associations, -> (user) do
+ preload(:board, label: :priorities)
+ .with_preferences_for(user)
+ end
+
scope :ordered, -> { order(:list_type, :position) }
+ # Loads list with preferences for given user
+ # if preferences exists for user or not
+ scope :with_preferences_for, -> (user) do
+ return unless user
+
+ includes(:list_user_preferences).where(list_user_preferences: { user_id: [user.id, nil] })
+ end
+
+ alias_method :preferences, :list_user_preferences
+
class << self
def destroyable_types
[:label]
@@ -29,6 +46,31 @@ class List < ApplicationRecord
end
end
+ def preferences_for(user)
+ return preferences.build unless user
+
+ if preferences.loaded?
+ preloaded_preferences_for(user)
+ else
+ preferences.find_or_initialize_by(user: user)
+ end
+ end
+
+ def preloaded_preferences_for(user)
+ user_preferences =
+ preferences.find do |preference|
+ preference.user_id == user.id
+ end
+
+ user_preferences || preferences.build(user: user)
+ end
+
+ def update_preferences_for(user, preferences = {})
+ return unless user
+
+ preferences_for(user).update(preferences)
+ end
+
def destroyable?
self.class.destroyable_types.include?(list_type&.to_sym)
end
@@ -43,6 +85,14 @@ class List < ApplicationRecord
def as_json(options = {})
super(options).tap do |json|
+ json[:collapsed] = false
+
+ if options.key?(:collapsed)
+ preferences = preferences_for(options[:current_user])
+
+ json[:collapsed] = preferences.collapsed?
+ end
+
if options.key?(:label)
json[:label] = label.as_json(
project: board.project,
diff --git a/app/models/list_user_preference.rb b/app/models/list_user_preference.rb
new file mode 100644
index 00000000000..fe1cc7d5425
--- /dev/null
+++ b/app/models/list_user_preference.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class ListUserPreference < ApplicationRecord
+ belongs_to :user
+ belongs_to :list
+
+ validates :user, presence: true
+ validates :list, presence: true
+ validates :user_id, uniqueness: { scope: :list_id, message: "should have only one list preference per user" }
+end
diff --git a/app/models/note.rb b/app/models/note.rb
index a12d1eb7243..3956ec192b1 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -89,6 +89,7 @@ class Note < ApplicationRecord
delegate :title, to: :noteable, allow_nil: true
validates :note, presence: true
+ validates :note, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }
validates :project, presence: true, if: :for_project_noteable?
# Attachments are deprecated and are handled by Markdown uploader
@@ -331,6 +332,10 @@ class Note < ApplicationRecord
cross_reference? && !all_referenced_mentionables_allowed?(user)
end
+ def visible_for?(user)
+ !cross_reference_not_visible_for?(user)
+ end
+
def award_emoji?
can_be_award_emoji? && contains_emoji_only?
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 66d5286196e..8f568a5b840 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -61,6 +61,8 @@ class Project < ApplicationRecord
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
:merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?,
+ :merge_requests_access_level, :issues_access_level, :wiki_access_level,
+ :snippets_access_level, :builds_access_level, :repository_access_level,
to: :project_feature, allow_nil: true
delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage
@@ -497,6 +499,7 @@ class Project < ApplicationRecord
# We require an alias to the project_mirror_data_table in order to use import_state in our queries
scope :joins_import_state, -> { joins("INNER JOIN project_mirror_data import_state ON import_state.project_id = projects.id") }
scope :for_group, -> (group) { where(group: group) }
+ scope :for_group_and_its_subgroups, ->(group) { where(namespace_id: group.self_and_descendants.select(:id)) }
class << self
# Searches for a list of projects based on the query given in `query`.
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index d08fcd8954d..0728c83005e 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -64,7 +64,12 @@ class JiraService < IssueTrackerService
end
def client
- @client ||= JIRA::Client.new(options)
+ @client ||= begin
+ JIRA::Client.new(options).tap do |client|
+ # Replaces JIRA default http client with our implementation
+ client.request_client = Gitlab::Jira::HttpClient.new(client.options)
+ end
+ end
end
def help
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index c9ee0653d86..41e63986286 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -200,6 +200,7 @@ class RemoteMirror < ApplicationRecord
result.password = '*****' if result.password
result.user = '*****' if result.user && result.user != 'git' # tokens or other data may be saved as user
result.to_s
+ rescue URI::Error
end
def ensure_remote!
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b957b9b0bdd..6f63cd32da4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -239,13 +239,13 @@ class Repository
def branch_exists?(branch_name)
return false unless raw_repository
- branch_names_include?(branch_name)
+ branch_names.include?(branch_name)
end
def tag_exists?(tag_name)
return false unless raw_repository
- tag_names_include?(tag_name)
+ tag_names.include?(tag_name)
end
def ref_exists?(ref)
@@ -565,10 +565,10 @@ class Repository
end
delegate :branch_names, to: :raw_repository
- cache_method_as_redis_set :branch_names, fallback: []
+ cache_method :branch_names, fallback: []
delegate :tag_names, to: :raw_repository
- cache_method_as_redis_set :tag_names, fallback: []
+ cache_method :tag_names, fallback: []
delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
cache_method :branch_count, fallback: 0
@@ -1130,10 +1130,6 @@ class Repository
@cache ||= Gitlab::RepositoryCache.new(self)
end
- def redis_set_cache
- @redis_set_cache ||= Gitlab::RepositorySetCache.new(self)
- end
-
def request_store_cache
@request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
end
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 9a2640db9ca..a19755d286a 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -9,7 +9,7 @@ class SystemNoteMetadata < ApplicationRecord
TYPES_WITH_CROSS_REFERENCES = %w[
commit cross_reference
close duplicate
- moved
+ moved merge
].freeze
ICON_TYPES = %w[
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 240c91da5b6..1ec04189482 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -186,9 +186,9 @@ class Todo < ApplicationRecord
def target_reference
if for_commit?
- target.reference_link_text(full: true)
+ target.reference_link_text
else
- target.to_reference(full: true)
+ target.to_reference
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 6107aaa7fca..9952bc7e1ad 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -645,6 +645,13 @@ class User < ApplicationRecord
end
end
+ # will_save_change_to_attribute? is used by Devise to check if it is necessary
+ # to clear any existing reset_password_tokens before updating an authentication_key
+ # and login in our case is a virtual attribute to allow login by username or email.
+ def will_save_change_to_login?
+ will_save_change_to_username? || will_save_change_to_email?
+ end
+
def unique_email
if !emails.exists?(email: email) && Email.exists?(email: email)
errors.add(:email, _('has already been taken'))
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index dd8c5d49cf4..fa252af55e4 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -5,6 +5,8 @@ class IssuePolicy < IssuablePolicy
# Make sure to sync this class checks with issue.rb to avoid security problems.
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
+ extend ProjectPolicy::ClassMethods
+
desc "User can read confidential issues"
condition(:can_read_confidential) do
@user && IssueCollection.new([@subject]).visible_to(@user).any?
@@ -14,13 +16,12 @@ class IssuePolicy < IssuablePolicy
condition(:confidential, scope: :subject) { @subject.confidential? }
rule { confidential & ~can_read_confidential }.policy do
- prevent :read_issue
+ prevent(*create_read_update_admin_destroy(:issue))
prevent :read_issue_iid
- prevent :update_issue
- prevent :admin_issue
- prevent :create_note
end
+ rule { ~can?(:read_issue) }.prevent :create_note
+
rule { locked }.policy do
prevent :reopen_issue
end
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index a3692857ff4..5ad7bdabdff 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -4,4 +4,10 @@ class MergeRequestPolicy < IssuablePolicy
rule { locked }.policy do
prevent :reopen_merge_request
end
+
+ # Only users who can read the merge request can comment.
+ # Although :read_merge_request is computed in the policy context,
+ # it would not be safe to prevent :create_note there, since
+ # note permissions are shared, and this would apply too broadly.
+ rule { ~can?(:read_merge_request) }.prevent :create_note
end
diff --git a/app/serializers/merge_request_noteable_entity.rb b/app/serializers/merge_request_noteable_entity.rb
new file mode 100644
index 00000000000..e22be6880bb
--- /dev/null
+++ b/app/serializers/merge_request_noteable_entity.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class MergeRequestNoteableEntity < Grape::Entity
+ include RequestAwareEntity
+
+ # Currently this attr is exposed to be used in app/assets/javascripts/notes/stores/getters.js
+ # in order to determine whether a noteable is an issue or an MR
+ expose :merge_params
+
+ expose :state
+ expose :source_branch
+ expose :target_branch
+ expose :diff_head_sha
+
+ expose :create_note_path do |merge_request|
+ project_notes_path(merge_request.project, target_type: 'merge_request', target_id: merge_request.id)
+ end
+
+ expose :preview_note_path do |merge_request|
+ preview_markdown_path(merge_request.project, target_type: 'MergeRequest', target_id: merge_request.iid)
+ end
+
+ expose :supports_suggestion?, as: :can_receive_suggestion
+
+ expose :create_issue_to_resolve_discussions_path do |merge_request|
+ presenter(merge_request).create_issue_to_resolve_discussions_path
+ end
+
+ expose :new_blob_path do |merge_request|
+ if presenter(merge_request).can_push_to_source_branch?
+ project_new_blob_path(merge_request.source_project, merge_request.source_branch)
+ end
+ end
+
+ expose :current_user do
+ expose :can_create_note do |merge_request|
+ can?(current_user, :create_note, merge_request)
+ end
+
+ expose :can_update do |merge_request|
+ can?(current_user, :update_merge_request, merge_request)
+ end
+ end
+
+ private
+
+ delegate :current_user, to: :request
+
+ def presenter(merge_request)
+ @presenters ||= {}
+ @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) # rubocop: disable CodeReuse/Presenter
+ end
+end
diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb
index 65132b4b215..cd33ffa702a 100644
--- a/app/serializers/merge_request_poll_widget_entity.rb
+++ b/app/serializers/merge_request_poll_widget_entity.rb
@@ -65,8 +65,6 @@ class MergeRequestPollWidgetEntity < IssuableEntity
end
end
- expose :supports_suggestion?, as: :can_receive_suggestion
-
expose :create_issue_to_resolve_discussions_path do |merge_request|
presenter(merge_request).create_issue_to_resolve_discussions_path
end
@@ -84,17 +82,9 @@ class MergeRequestPollWidgetEntity < IssuableEntity
presenter(merge_request).can_cherry_pick_on_current_merge_request?
end
- expose :can_create_note do |merge_request|
- can?(current_user, :create_note, merge_request)
- end
-
expose :can_create_issue do |merge_request|
can?(current_user, :create_issue, merge_request.project)
end
-
- expose :can_update do |merge_request|
- can?(current_user, :update_merge_request, merge_request)
- end
end
expose :can_push_to_source_branch do |merge_request|
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
index bd2e682a122..aa67cd1f39e 100644
--- a/app/serializers/merge_request_serializer.rb
+++ b/app/serializers/merge_request_serializer.rb
@@ -13,6 +13,8 @@ class MergeRequestSerializer < BaseSerializer
MergeRequestSidebarExtrasEntity
when 'basic'
MergeRequestBasicEntity
+ when 'noteable'
+ MergeRequestNoteableEntity
else
# fallback to widget for old poll requests without `serializer` set
MergeRequestWidgetEntity
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index c8088608cb0..2f2c42a7387 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -3,10 +3,6 @@
class MergeRequestWidgetEntity < Grape::Entity
include RequestAwareEntity
- # Currently this attr is exposed to be used in app/assets/javascripts/notes/stores/getters.js
- # in order to determine whether a noteable is an issue or an MR
- expose :merge_params
-
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
end
@@ -35,18 +31,10 @@ class MergeRequestWidgetEntity < Grape::Entity
cached_widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
- expose :create_note_path do |merge_request|
- project_notes_path(merge_request.project, target_type: 'merge_request', target_id: merge_request.id)
- end
-
expose :commit_change_content_path do |merge_request|
commit_change_content_project_merge_request_path(merge_request.project, merge_request)
end
- expose :preview_note_path do |merge_request|
- preview_markdown_path(merge_request.project, target_type: 'MergeRequest', target_id: merge_request.iid)
- end
-
expose :conflicts_docs_path do |merge_request|
help_page_path('user/project/merge_requests/resolve_conflicts.md')
end
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
index 8115585b7a8..e06a87c4763 100644
--- a/app/services/application_settings/update_service.rb
+++ b/app/services/application_settings/update_service.rb
@@ -6,6 +6,8 @@ module ApplicationSettings
attr_reader :params, :application_setting
+ MARKDOWN_CACHE_INVALIDATING_PARAMS = %w(asset_proxy_enabled asset_proxy_url asset_proxy_secret_key asset_proxy_whitelist).freeze
+
def execute
validate_classification_label(application_setting, :external_authorization_service_default_label) unless bypass_external_auth?
@@ -25,7 +27,13 @@ module ApplicationSettings
params[:usage_stats_set_by_user_id] = current_user.id
end
- @application_setting.update(@params)
+ @application_setting.assign_attributes(params)
+
+ if invalidate_markdown_cache?
+ @application_setting[:local_markdown_version] = @application_setting.local_markdown_version + 1
+ end
+
+ @application_setting.save
end
private
@@ -41,6 +49,11 @@ module ApplicationSettings
@application_setting.add_to_outbound_local_requests_whitelist(values_array)
end
+ def invalidate_markdown_cache?
+ !params.key?(:local_markdown_version) &&
+ (@application_setting.changes.keys & MARKDOWN_CACHE_INVALIDATING_PARAMS).any?
+ end
+
def update_terms(terms)
return unless terms.present?
diff --git a/app/services/base_count_service.rb b/app/services/base_count_service.rb
index ad1647842b8..cfad2dd9265 100644
--- a/app/services/base_count_service.rb
+++ b/app/services/base_count_service.rb
@@ -35,7 +35,7 @@ class BaseCountService
end
def cache_key
- raise NotImplementedError, 'cache_key must be implemented and return a String'
+ raise NotImplementedError, 'cache_key must be implemented and return a String, Array, or Hash'
end
# subclasses can override to add any specific options, such as
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 3e968c8f707..c39edd5c114 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -44,6 +44,10 @@ class BaseService
model.errors.add(:visibility_level, "#{level_name} has been restricted by your GitLab administrator")
end
+ def visibility_level
+ params[:visibility].is_a?(String) ? Gitlab::VisibilityLevel.level_value(params[:visibility]) : params[:visibility_level]
+ end
+
private
def error(message, http_status = nil)
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
index 5cf5f14a55b..1f20ec8df9e 100644
--- a/app/services/boards/lists/list_service.rb
+++ b/app/services/boards/lists/list_service.rb
@@ -6,7 +6,7 @@ module Boards
def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
- board.lists.preload_associations
+ board.lists.preload_associations(current_user)
end
end
end
diff --git a/app/services/boards/lists/update_service.rb b/app/services/boards/lists/update_service.rb
new file mode 100644
index 00000000000..2ddeb6f0bd8
--- /dev/null
+++ b/app/services/boards/lists/update_service.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Boards
+ module Lists
+ class UpdateService < Boards::BaseService
+ def execute(list)
+ return not_authorized if preferences? && !can_read?(list)
+ return not_authorized if position? && !can_admin?(list)
+
+ if update_preferences(list) || update_position(list)
+ success(list: list)
+ else
+ error(list.errors.messages, 422)
+ end
+ end
+
+ def update_preferences(list)
+ return unless preferences?
+
+ list.update_preferences_for(current_user, preferences)
+ end
+
+ def update_position(list)
+ return unless position?
+
+ move_service = Boards::Lists::MoveService.new(parent, current_user, params)
+
+ move_service.execute(list)
+ end
+
+ def preferences
+ { collapsed: Gitlab::Utils.to_boolean(params[:collapsed]) }
+ end
+
+ def not_authorized
+ error("Not authorized", 403)
+ end
+
+ def preferences?
+ params.has_key?(:collapsed)
+ end
+
+ def position?
+ params.has_key?(:position)
+ end
+
+ def can_read?(list)
+ Ability.allowed?(current_user, :read_list, parent)
+ end
+
+ def can_admin?(list)
+ Ability.allowed?(current_user, :admin_list, parent)
+ end
+ end
+ end
+end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index cdcc4b15bea..29317f1176e 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -15,7 +15,8 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Limit::Size,
Gitlab::Ci::Pipeline::Chain::Populate,
Gitlab::Ci::Pipeline::Chain::Create,
- Gitlab::Ci::Pipeline::Chain::Limit::Activity].freeze
+ Gitlab::Ci::Pipeline::Chain::Limit::Activity,
+ Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, **options, &block)
@pipeline = Ci::Pipeline.new
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index 3c6803d24e6..65d08966802 100644
--- a/app/services/clusters/applications/check_installation_progress_service.rb
+++ b/app/services/clusters/applications/check_installation_progress_service.rb
@@ -2,24 +2,7 @@
module Clusters
module Applications
- class CheckInstallationProgressService < BaseHelmService
- def execute
- return unless operation_in_progress?
-
- case installation_phase
- when Gitlab::Kubernetes::Pod::SUCCEEDED
- on_success
- when Gitlab::Kubernetes::Pod::FAILED
- on_failed
- else
- check_timeout
- end
- rescue Kubeclient::HttpError => e
- log_error(e)
-
- app.make_errored!("Kubernetes error: #{e.error_code}")
- end
-
+ class CheckInstallationProgressService < CheckProgressService
private
def operation_in_progress?
@@ -32,10 +15,6 @@ module Clusters
remove_installation_pod
end
- def on_failed
- app.make_errored!("Operation failed. Check pod logs for #{pod_name} for more details.")
- end
-
def check_timeout
if timed_out?
begin
@@ -54,18 +33,6 @@ module Clusters
def timed_out?
Time.now.utc - app.updated_at.utc > ClusterWaitForAppInstallationWorker::TIMEOUT
end
-
- def remove_installation_pod
- helm_api.delete_pod!(pod_name)
- end
-
- def installation_phase
- helm_api.status(pod_name)
- end
-
- def installation_errors
- helm_api.log(pod_name)
- end
end
end
end
diff --git a/app/services/clusters/applications/check_progress_service.rb b/app/services/clusters/applications/check_progress_service.rb
new file mode 100644
index 00000000000..4a07b955f8e
--- /dev/null
+++ b/app/services/clusters/applications/check_progress_service.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class CheckProgressService < BaseHelmService
+ def execute
+ return unless operation_in_progress?
+
+ case pod_phase
+ when Gitlab::Kubernetes::Pod::SUCCEEDED
+ on_success
+ when Gitlab::Kubernetes::Pod::FAILED
+ on_failed
+ else
+ check_timeout
+ end
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+
+ app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
+ end
+
+ private
+
+ def operation_in_progress?
+ raise NotImplementedError
+ end
+
+ def on_success
+ raise NotImplementedError
+ end
+
+ def pod_name
+ raise NotImplementedError
+ end
+
+ def on_failed
+ app.make_errored!(_('Operation failed. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
+ end
+
+ def timed_out?
+ raise NotImplementedError
+ end
+
+ def pod_phase
+ helm_api.status(pod_name)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/check_uninstall_progress_service.rb b/app/services/clusters/applications/check_uninstall_progress_service.rb
index e51d84ef052..6a618d61c4f 100644
--- a/app/services/clusters/applications/check_uninstall_progress_service.rb
+++ b/app/services/clusters/applications/check_uninstall_progress_service.rb
@@ -2,26 +2,13 @@
module Clusters
module Applications
- class CheckUninstallProgressService < BaseHelmService
- def execute
- return unless app.uninstalling?
-
- case installation_phase
- when Gitlab::Kubernetes::Pod::SUCCEEDED
- on_success
- when Gitlab::Kubernetes::Pod::FAILED
- on_failed
- else
- check_timeout
- end
- rescue Kubeclient::HttpError => e
- log_error(e)
+ class CheckUninstallProgressService < CheckProgressService
+ private
- app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
+ def operation_in_progress?
+ app.uninstalling?
end
- private
-
def on_success
app.post_uninstall
app.destroy!
@@ -31,10 +18,6 @@ module Clusters
remove_installation_pod
end
- def on_failed
- app.make_errored!(_('Operation failed. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
- end
-
def check_timeout
if timed_out?
app.make_errored!(_('Operation timed out. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
@@ -50,14 +33,6 @@ module Clusters
def timed_out?
Time.now.utc - app.updated_at.utc > WaitForUninstallAppWorker::TIMEOUT
end
-
- def remove_installation_pod
- helm_api.delete_pod!(pod_name)
- end
-
- def installation_phase
- helm_api.status(pod_name)
- end
end
end
end
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 6e5bf823cc7..0aa76df35ba 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -12,7 +12,7 @@ class CreateSnippetService < BaseService
PersonalSnippet.new(params)
end
- unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, snippet.visibility_level)
deny_visibility_level(snippet)
return snippet
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index e78e5d5fc2c..1dd22d7a3ae 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -68,9 +68,5 @@ module Groups
true
end
-
- def visibility_level
- params[:visibility].present? ? Gitlab::VisibilityLevel.level_value(params[:visibility]) : params[:visibility_level]
- end
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 89dc4375c63..942a45286b2 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -5,9 +5,11 @@ module Projects
include ValidatesClassificationLabel
def initialize(user, params)
- @current_user, @params = user, params.dup
- @skip_wiki = @params.delete(:skip_wiki)
+ @current_user, @params = user, params.dup
+ @skip_wiki = @params.delete(:skip_wiki)
@initialize_with_readme = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_readme))
+ @import_data = @params.delete(:import_data)
+ @relations_block = @params.delete(:relations_block)
end
def execute
@@ -15,14 +17,11 @@ module Projects
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
- import_data = params.delete(:import_data)
- relations_block = params.delete(:relations_block)
-
@project = Project.new(params)
# Make sure that the user is allowed to use the specified visibility level
- unless Gitlab::VisibilityLevel.allowed_for?(current_user, @project.visibility_level)
- deny_visibility_level(@project)
+ if project_visibility.restricted?
+ deny_visibility_level(@project, project_visibility.visibility_level)
return @project
end
@@ -44,7 +43,7 @@ module Projects
@project.namespace_id = current_user.namespace_id
end
- relations_block&.call(@project)
+ @relations_block&.call(@project)
yield(@project) if block_given?
validate_classification_label(@project, :external_authorization_classification_label)
@@ -54,7 +53,7 @@ module Projects
@project.creator = current_user
- save_project_and_import_data(import_data)
+ save_project_and_import_data
after_create_actions if @project.persisted?
@@ -129,9 +128,9 @@ module Projects
!@project.feature_available?(:wiki, current_user) || @skip_wiki
end
- def save_project_and_import_data(import_data)
+ def save_project_and_import_data
Project.transaction do
- @project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data
+ @project.create_or_update_import_data(data: @import_data[:data], credentials: @import_data[:credentials]) if @import_data
if @project.save
unless @project.gitlab_project_import?
@@ -192,5 +191,11 @@ module Projects
fail(error: @project.errors.full_messages.join(', '))
end
end
+
+ def project_visibility
+ @project_visibility ||= Gitlab::VisibilityLevelChecker
+ .new(current_user, @project, project_params: { import_data: @import_data })
+ .level_restricted?
+ end
end
end
diff --git a/app/services/projects/lfs_pointers/lfs_link_service.rb b/app/services/projects/lfs_pointers/lfs_link_service.rb
index e3c956250f0..38de2af9c1e 100644
--- a/app/services/projects/lfs_pointers/lfs_link_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_link_service.rb
@@ -4,6 +4,8 @@
module Projects
module LfsPointers
class LfsLinkService < BaseService
+ BATCH_SIZE = 1000
+
# Accept an array of oids to link
#
# Returns an array with the oid of the existent lfs objects
@@ -18,16 +20,33 @@ module Projects
# rubocop: disable CodeReuse/ActiveRecord
def link_existing_lfs_objects(oids)
- existent_lfs_objects = LfsObject.where(oid: oids)
+ all_existing_objects = []
+ iterations = 0
+
+ LfsObject.where(oid: oids).each_batch(of: BATCH_SIZE) do |existent_lfs_objects|
+ next unless existent_lfs_objects.any?
+
+ iterations += 1
+ not_linked_lfs_objects = existent_lfs_objects.where.not(id: project.all_lfs_objects)
+ project.all_lfs_objects << not_linked_lfs_objects
- return [] unless existent_lfs_objects.any?
+ all_existing_objects += existent_lfs_objects.pluck(:oid)
+ end
- not_linked_lfs_objects = existent_lfs_objects.where.not(id: project.all_lfs_objects)
- project.all_lfs_objects << not_linked_lfs_objects
+ log_lfs_link_results(all_existing_objects.count, iterations)
- existent_lfs_objects.pluck(:oid)
+ all_existing_objects
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def log_lfs_link_results(lfs_objects_linked_count, iterations)
+ Gitlab::Import::Logger.info(
+ class: self.class.name,
+ project_id: project.id,
+ project_path: project.full_path,
+ lfs_objects_linked_count: lfs_objects_linked_count,
+ iterations: iterations)
+ end
end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index ee7223d6349..1b48b20e28b 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -67,7 +67,7 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'assignee'))
end
- # Called when the assignees of an Issue is changed or removed
+ # Called when the assignees of an issuable is changed or removed
#
# issuable - Issuable object (responds to assignees)
# project - Project owning noteable
@@ -88,10 +88,12 @@ module SystemNoteService
def change_issuable_assignees(issuable, project, author, old_assignees)
unassigned_users = old_assignees - issuable.assignees
added_users = issuable.assignees.to_a - old_assignees
-
text_parts = []
- text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
- text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
+
+ Gitlab::I18n.with_default_locale do
+ text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
+ text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
+ end
body = text_parts.join(' and ')
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 0ea230a44a1..b1256df35d6 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -314,11 +314,9 @@ class TodoService
end
def reject_users_without_access(users, parent, target)
- if target.is_a?(Note) && target.for_issuable?
- target = target.noteable
- end
+ target = target.noteable if target.is_a?(Note)
- if target.is_a?(Issuable)
+ if target.respond_to?(:to_ability_name)
select_users(users, :"read_#{target.to_ability_name}", target)
else
select_users(users, :read_project, parent)
diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb
index 2969c360de5..a294812ef9e 100644
--- a/app/services/update_snippet_service.rb
+++ b/app/services/update_snippet_service.rb
@@ -12,7 +12,7 @@ class UpdateSnippetService < BaseService
def execute
# check that user is allowed to set specified visibility_level
- new_visibility = params[:visibility_level]
+ new_visibility = visibility_level
if new_visibility && new_visibility.to_i != snippet.visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb
index 1ac69601d18..3efdd0aa1d9 100644
--- a/app/uploaders/personal_file_uploader.rb
+++ b/app/uploaders/personal_file_uploader.rb
@@ -6,6 +6,10 @@ class PersonalFileUploader < FileUploader
options.storage_path
end
+ def self.workhorse_local_upload_path
+ File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
+ end
+
def self.base_dir(model, _store = nil)
# base_dir is the path seen by the user when rendering Markdown, so
# it should be the same for both local and object storage. It is
diff --git a/app/views/admin/application_settings/_ip_limits.html.haml b/app/views/admin/application_settings/_ip_limits.html.haml
index 67a04fcf698..9512c1837bf 100644
--- a/app/views/admin/application_settings/_ip_limits.html.haml
+++ b/app/views/admin/application_settings/_ip_limits.html.haml
@@ -4,7 +4,7 @@
%fieldset
.form-group
.form-check
- = f.check_box :throttle_unauthenticated_enabled, class: 'form-check-input'
+ = f.check_box :throttle_unauthenticated_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_unauthenticated_checkbox' }
= f.label :throttle_unauthenticated_enabled, class: 'form-check-label' do
Enable unauthenticated request rate limit
%span.form-text.text-muted
@@ -17,7 +17,7 @@
= f.number_field :throttle_unauthenticated_period_in_seconds, class: 'form-control'
.form-group
.form-check
- = f.check_box :throttle_authenticated_api_enabled, class: 'form-check-input'
+ = f.check_box :throttle_authenticated_api_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_authenticated_api_checkbox' }
= f.label :throttle_authenticated_api_enabled, class: 'form-check-label' do
Enable authenticated API request rate limit
%span.form-text.text-muted
@@ -30,7 +30,7 @@
= f.number_field :throttle_authenticated_api_period_in_seconds, class: 'form-control'
.form-group
.form-check
- = f.check_box :throttle_authenticated_web_enabled, class: 'form-check-input'
+ = f.check_box :throttle_authenticated_web_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_authenticated_web_checkbox' }
= f.label :throttle_authenticated_web_enabled, class: 'form-check-label' do
Enable authenticated web request rate limit
%span.form-text.text-muted
@@ -42,4 +42,4 @@
= f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'label-bold'
= f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control'
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit 'Save changes', class: "btn btn-success", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/admin/application_settings/_spam.html.haml b/app/views/admin/application_settings/_spam.html.haml
index d24e46b2815..f0a19075115 100644
--- a/app/views/admin/application_settings/_spam.html.haml
+++ b/app/views/admin/application_settings/_spam.html.haml
@@ -7,11 +7,15 @@
= f.check_box :recaptcha_enabled, class: 'form-check-input'
= f.label :recaptcha_enabled, class: 'form-check-label' do
Enable reCAPTCHA
- - recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions'
- - recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url }
%span.form-text.text-muted#recaptcha_help_block
- = _('Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe }
-
+ = _('Helps prevent bots from creating accounts.')
+ .form-group
+ .form-check
+ = f.check_box :login_recaptcha_protection_enabled, class: 'form-check-input'
+ = f.label :login_recaptcha_protection_enabled, class: 'form-check-label' do
+ Enable reCAPTCHA for login
+ %span.form-text.text-muted#recaptcha_help_block
+ = _('Helps prevent bots from brute-force attacks.')
.form-group
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'label-bold'
= f.text_field :recaptcha_site_key, class: 'form-control'
@@ -21,6 +25,7 @@
.form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'label-bold'
+ .form-group
= f.text_field :recaptcha_private_key, class: 'form-control'
.form-group
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index 26fd745f45f..3a4d901ca1d 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -13,7 +13,7 @@
.settings-content
= render 'performance'
-%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded_by_default?) }
+%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'ip_limits_section' } }
.settings-header
%h4
= _('User and IP Rate Limits')
diff --git a/app/views/admin/application_settings/reporting.html.haml b/app/views/admin/application_settings/reporting.html.haml
index 46e3d1c4570..c60e44b3864 100644
--- a/app/views/admin/application_settings/reporting.html.haml
+++ b/app/views/admin/application_settings/reporting.html.haml
@@ -9,7 +9,9 @@
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
- = _('Enable reCAPTCHA or Akismet and set IP limits.')
+ - recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions'
+ - recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url }
+ = _('Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe }
.settings-content
= render 'spam'
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 8cdfc7369a0..fdb71d3a221 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -2,41 +2,49 @@
.todo-avatar
= author_avatar(todo, size: 40)
- .todo-item.todo-block
- .todo-title.title
+ .todo-item.todo-block.align-self-center
+ .todo-title
- unless todo.build_failed? || todo.unmergeable?
= todo_target_state_pill(todo)
- .title-item.author-name
+ %span.title-item.author-name.bold
- if todo.author
= link_to_author(todo, self_added: todo.self_added?)
- else
(removed)
- .title-item.action-name
+ %span.title-item.action-name
= todo_action_name(todo)
- .title-item.todo-label
+ %span.title-item.todo-label.todo-target-link
- if todo.target
= todo_target_link(todo)
- else
- (removed)
+ = _("(removed)")
+
+ %span.title-item.todo-target-title
+ = todo_target_title(todo)
+
+ %span.title-item.todo-project.todo-label
+ at
+ = todo_parent_path(todo)
- if todo.self_assigned?
- .title-item.action-name
+ %span.title-item.action-name
to yourself
- .title-item
+ %span.title-item
&middot;
- .title-item
+ %span.title-item.todo-timestamp
#{time_ago_with_tooltip(todo.created_at)}
= todo_due_date(todo)
- .todo-body
- .todo-note.break-word
- .md
- = first_line_in_markdown(todo, :body, 150, project: todo.project)
+ - if todo.note.present?
+ .todo-body
+ .todo-note.break-word
+ .md
+ = first_line_in_markdown(todo, :body, 150, project: todo.project)
- if todo.pending?
.todo-actions
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 0b1d3d1ddb3..6e9efcb0597 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -16,7 +16,7 @@
- else
= link_to _('Forgot your password?'), new_password_path(:user)
%div
- - if captcha_enabled?
+ - if captcha_enabled? || captcha_on_login_required?
= recaptcha_tags
.submit-container.move-submit-down
diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml
index 733cb36cc3d..c3c7d102b28 100644
--- a/app/views/groups/_group_admin_settings.html.haml
+++ b/app/views/groups/_group_admin_settings.html.haml
@@ -9,7 +9,7 @@
Allow projects within this group to use Git LFS
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%br/
- %span.descr This setting can be overridden in each project.
+ %span This setting can be overridden in each project.
.form-group.row
.col-sm-2.col-form-label
= f.label s_('ProjectCreationLevel|Allowed to create projects')
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 0c8f86c2822..6d06bb246cb 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -14,7 +14,7 @@
.settings-content
= render 'groups/settings/general'
-%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded) }
+%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded), data: { qa_selector: 'permission_lfs_2fa_section' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
= _('Permissions, LFS, 2FA')
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 94a938021f9..4c88660ccb9 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -14,7 +14,7 @@
%span.d-block
- group_link = link_to @group.name, group_path(@group)
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
- %span.descr.text-muted= share_with_group_lock_help_text(@group)
+ %span.js-descr.text-muted= share_with_group_lock_help_text(@group)
.form-group.append-bottom-default
.form-check
@@ -31,4 +31,4 @@
= render 'groups/settings/two_factor_auth', f: f
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
- = f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit'
+ = f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 271b73326fa..68abfd3f61f 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -36,7 +36,6 @@
= stylesheet_link_tag "print", media: "print"
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
- = stylesheet_link_tag 'csslab' if Feature.enabled?(:csslab)
= stylesheet_link_tag "highlight/themes/#{user_color_scheme}", media: "all"
diff --git a/app/views/layouts/_snowplow.html.haml b/app/views/layouts/_snowplow.html.haml
index 5f5c5e984c5..d7ff5ad1094 100644
--- a/app/views/layouts/_snowplow.html.haml
+++ b/app/views/layouts/_snowplow.html.haml
@@ -7,23 +7,4 @@
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","#{asset_url('snowplow/sp.js')}","snowplow"));
- window.snowplow('newTracker', '#{Gitlab::SnowplowTracker::NAMESPACE}', '#{Gitlab::CurrentSettings.snowplow_collector_hostname}', {
- appId: '#{Gitlab::CurrentSettings.snowplow_site_id}',
- cookieDomain: '#{Gitlab::CurrentSettings.snowplow_cookie_domain}',
- userFingerprint: false,
- respectDoNotTrack: true,
- forceSecureTracker: true,
- post: true,
- contexts: { webPage: true },
- stateStorageStrategy: "localStorage"
- });
-
- window.snowplow('enableActivityTracking', 30, 30);
- window.snowplow('trackPageView');
-
-- return unless Feature.enabled?(:additional_snowplow_tracking, @group)
-
-= javascript_tag nonce: true do
- :plain
- window.snowplow('enableFormTracking');
- window.snowplow('enableLinkClickTracking');
+ window.snowplowOptions = #{Gitlab::Tracking.snowplow_options(@group).to_json}
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index cb39c830170..9e92ced9f89 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -261,7 +261,7 @@
%span
= _('Metrics and profiling')
= nav_link(path: 'application_settings#network') do
- = link_to network_admin_application_settings_path, title: _('Network') do
+ = link_to network_admin_application_settings_path, title: _('Network'), data: { qa_selector: 'admin_settings_network_item' } do
%span
= _('Network')
- if template_exists?('admin/application_settings/geo')
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 48c9f19f89f..c1f4b3adfec 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -147,7 +147,7 @@
= _('Settings')
%li.divider.fly-out-top-item
= nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: _('General') do
+ = link_to edit_group_path(@group), title: _('General'), data: { qa_selector: 'general_settings_link' } do
%span
= _('General')
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index b93d95ef02f..bd61db3ee76 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -1,9 +1,5 @@
-<%= sanitize_name(@issue.author_name) %> <%= 'created an issue:' %>
+<%= sanitize_name(@issue.author_name) %> <%= 'created an issue:' %> <%= url_for(project_issue_url(@issue.project, @issue)) %>
-<% if @issue.assignees.any? -%>
- <%= assignees_label(@issue) %>
-<% end %>
+<%= assignees_label(@issue) if @issue.assignees.any? %>
-<% if @issue.description -%>
- <%= @issue.description %>
-<% end %>
+<%= @issue.description %>
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 6c0d7b1e60b..f4b0ed0f886 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,8 +1,8 @@
-<%= @merge_request.author_name %> <%= 'created a merge request:' %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
+<%= sanitize_name(@merge_request.author_name) %> <%= 'created a merge request:' %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
<%= 'Author:' %> <%= @merge_request.author_name %>
-<%= assignees_label(@merge_request) %>
+<%= assignees_label(@merge_request) if @merge_request.assignees.any? %>
<%= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter %>
<%= @merge_request.description %>
diff --git a/app/views/projects/_merge_request_merge_checks_settings.html.haml b/app/views/projects/_merge_request_merge_checks_settings.html.haml
index c21d333f21a..d3fcb52422b 100644
--- a/app/views/projects/_merge_request_merge_checks_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_checks_settings.html.haml
@@ -7,7 +7,7 @@
= form.check_box :only_allow_merge_if_pipeline_succeeds, class: 'form-check-input'
= form.label :only_allow_merge_if_pipeline_succeeds, class: 'form-check-label' do
= s_('ProjectSettings|Pipelines must succeed')
- .descr.text-secondary
+ .text-secondary
= s_('ProjectSettings|Pipelines need to be configured to enable this feature.')
= link_to icon('question-circle'),
help_page_path('ci/merge_request_pipelines/index.md',
diff --git a/app/views/projects/_merge_request_merge_method_settings.html.haml b/app/views/projects/_merge_request_merge_method_settings.html.haml
index 47c311f42d0..1be7f7bb418 100644
--- a/app/views/projects/_merge_request_merge_method_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_method_settings.html.haml
@@ -7,14 +7,14 @@
= form.radio_button :merge_method, :merge, class: "js-merge-method-radio form-check-input"
= label_tag :project_merge_method_merge, class: 'form-check-label' do
= s_('ProjectSettings|Merge commit')
- .descr.text-secondary
+ .text-secondary
= s_('ProjectSettings|Every merge creates a merge commit')
.form-check.mb-2
= form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio form-check-input"
= label_tag :project_merge_method_rebase_merge, class: 'form-check-label' do
= s_('ProjectSettings|Merge commit with semi-linear history')
- .descr.text-secondary
+ .text-secondary
= s_('ProjectSettings|Every merge creates a merge commit')
%br
= s_('ProjectSettings|Fast-forward merges only')
@@ -25,7 +25,7 @@
= form.radio_button :merge_method, :ff, class: "js-merge-method-radio qa-radio-button-merge-ff form-check-input"
= label_tag :project_merge_method_ff, class: 'form-check-label' do
= s_('ProjectSettings|Fast-forward merge')
- .descr.text-secondary
+ .text-secondary
= s_('ProjectSettings|No merge commits are created')
%br
= s_('ProjectSettings|Fast-forward merges only')
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 95c5eb32c7f..cf273aab108 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -9,6 +9,6 @@
= render "projects/blob/auxiliary_viewer", blob: blob
#blob-content-holder.blob-content-holder
- %article.file-holder{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ %article.file-holder
= render 'projects/blob/header', blob: blob
= render 'projects/blob/content', blob: blob
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
index 3e893343165..46e76e4d175 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -1,5 +1,5 @@
- if markup?(@blob.name)
- .file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ .file-content.md.md-file
= markup(@blob.name, @content)
- else
.diff-file
diff --git a/app/views/projects/blob/viewers/_markup.html.haml b/app/views/projects/blob/viewers/_markup.html.haml
index abc74b66e90..c71df29354b 100644
--- a/app/views/projects/blob/viewers/_markup.html.haml
+++ b/app/views/projects/blob/viewers/_markup.html.haml
@@ -1,4 +1,4 @@
- blob = viewer.blob
- context = blob.respond_to?(:rendered_markup) ? { rendered: blob.rendered_markup } : {}
-.file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+.file-content.md.md-file
= markup(blob.name, blob.data, context)
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index a766dd51463..6c77036a85b 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -5,7 +5,7 @@
= render partial: 'signature', object: @commit.signature
%strong
#{ s_('CommitBoxTitle|Commit') }
- %span.commit-sha= @commit.short_id
+ %span.commit-sha{ data: { qa_selector: 'commit_sha_content' } }= @commit.short_id
= clipboard_button(text: @commit.id, title: _('Copy commit SHA to clipboard'))
%span.d-none.d-sm-inline= _('authored')
#{time_ago_with_tooltip(@commit.authored_date)}
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index 752be02443c..ef2ab4c698e 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -22,7 +22,8 @@
.table-section.section-15{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' }= _("Created")
- %span.table-mobile-content= time_ago_with_tooltip(deployment.created_at)
+ - if deployment.deployed_at
+ %span.table-mobile-content= time_ago_with_tooltip(deployment.deployed_at)
.table-section.section-20.table-button-footer{ role: 'gridcell' }
.btn-group.table-action-buttons
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index af3bd8dcd69..ea166d622eb 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -6,6 +6,7 @@
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- suggest_changes_help_path = help_page_path('user/discussions/index.md', anchor: 'suggest-changes')
+- number_of_pipelines = @pipelines.size
.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), lock_version: @merge_request.lock_version } }
= render "projects/merge_requests/mr_title"
@@ -41,11 +42,11 @@
= tab_link_for @merge_request, :commits do
= _("Commits")
%span.badge.badge-pill= @commits_count
- - if @pipelines.any?
+ - if number_of_pipelines.nonzero?
%li.pipelines-tab
= tab_link_for @merge_request, :pipelines do
= _("Pipelines")
- %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
+ %span.badge.badge-pill.js-pipelines-mr-count= number_of_pipelines
%li.diffs-tab.qa-diffs-tab
= tab_link_for @merge_request, :diffs do
= _("Changes")
@@ -63,21 +64,21 @@
%script.js-notes-data{ type: "application/json" }= initial_notes_data(true).to_json.html_safe
.issuable-discussion.js-vue-notes-event
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request).to_json,
- noteable_data: serialize_issuable(@merge_request),
+ noteable_data: serialize_issuable(@merge_request, serializer: 'noteable'),
noteable_type: 'MergeRequest',
target_type: 'merge_request',
help_page_path: suggest_changes_help_path,
- current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json} }
+ current_user_data: @current_user_data} }
#commits.commits.tab-pane
-# This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane
- - if @pipelines.any?
+ - if number_of_pipelines.nonzero?
= render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
#js-diffs-app.diffs.tab-pane{ data: { "is-locked" => @merge_request.discussion_locked?,
endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
help_page_path: suggest_changes_help_path,
- current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json,
+ current_user_data: @current_user_data,
project_path: project_path(@merge_request.project),
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
is_fluid_layout: fluid_layout.to_s,
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 5cc6b5a173b..e1797e6db2a 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -21,7 +21,7 @@
.form-actions
- if @milestone.new_record?
- = f.submit _('Create milestone'), class: 'btn-create btn qa-milestone-create-button'
+ = f.submit _('Create milestone'), class: 'btn-success btn qa-milestone-create-button'
= link_to _('Cancel'), project_milestones_path(@project), class: 'btn btn-cancel'
- else
= f.submit _('Save changes'), class: 'btn-success btn'
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index 6f8a93fbcf5..84f0900d9c1 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -50,7 +50,7 @@
- @project.remote_mirrors.each_with_index do |mirror, index|
- next if mirror.new_record?
%tr.qa-mirrored-repository-row.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?) }
- %td.qa-mirror-repository-url= mirror.safe_url
+ %td.qa-mirror-repository-url= mirror.safe_url || _('Invalid URL')
%td= _('Push')
%td
= mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never')
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index de1b95692d6..2f277e8147a 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -15,7 +15,7 @@
.footer-block.row-content-block
= service_save_button(@service)
&nbsp;
- = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
+ = link_to _('Cancel'), project_settings_integrations_path(@project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
%hr
diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml
index 16e48814578..7748a7a6a8e 100644
--- a/app/views/projects/services/_index.html.haml
+++ b/app/views/projects/services/_index.html.haml
@@ -1,8 +1,8 @@
.row.prepend-top-default.append-bottom-default
.col-lg-4
%h4.prepend-top-0
- Project services
- %p Project services allow you to integrate GitLab with other applications
+ = s_("ProjectService|Project services")
+ %p= s_("ProjectService|Project services allow you to integrate GitLab with other applications")
.col-lg-8
%table.table
%colgroup
@@ -13,12 +13,12 @@
%thead
%tr
%th
- %th Service
- %th.d-none.d-sm-block Description
- %th Last edit
+ %th= s_("ProjectService|Service")
+ %th.d-none.d-sm-block= _("Description")
+ %th= s_("ProjectService|Last edit")
- @services.sort_by(&:title).each do |service|
%tr
- %td{ "aria-label" => "#{service.title}: status " + (service.activated? ? "on" : "off") }
+ %td{ "aria-label" => (service.activated? ? s_("ProjectService|%{service_title}: status on") : s_("ProjectService|%{service_title}: status off")) % { service_title: service.title } }
= boolean_to_icon service.activated?
%td
= link_to edit_project_service_path(@project, service.to_param) do
diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml
index df1fd583670..fc20bc52d1c 100644
--- a/app/views/projects/services/edit.html.haml
+++ b/app/views/projects/services/edit.html.haml
@@ -1,6 +1,6 @@
-- breadcrumb_title "Integrations"
-- page_title @service.title, "Services"
-- add_to_breadcrumbs("Settings", edit_project_path(@project))
+- breadcrumb_title s_("ProjectService|Integrations")
+- page_title @service.title, s_("ProjectService|Services")
+- add_to_breadcrumbs(s_("ProjectService|Settings"), edit_project_path(@project))
= render 'deprecated_message' if @service.deprecation_message
diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
index 82c1d57c97e..395df502ddb 100644
--- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
@@ -1,6 +1,6 @@
-- run_actions_text = "Perform common operations on GitLab project: #{@project.full_name}"
+- run_actions_text = s_("ProjectService|Perform common operations on GitLab project: %{project_name}") % { project_name: @project.full_name }
-%p To set up this service:
+%p= s_("ProjectService|To set up this service:")
%ul.list-unstyled.indent-list
%li
1.
@@ -18,67 +18,67 @@
.help-form
.form-group
- = label_tag :display_name, 'Display name', class: 'col-12 col-form-label label-bold'
+ = label_tag :display_name, _('Display name'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#display_name', class: 'input-group-text')
.form-group
- = label_tag :description, 'Description', class: 'col-12 col-form-label label-bold'
+ = label_tag :description, _('Description'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#description', class: 'input-group-text')
.form-group
- = label_tag nil, 'Command trigger word', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, s_('MattermostService|Command trigger word'), class: 'col-12 col-form-label label-bold'
.col-12
- %p Fill in the word that works best for your team.
+ %p= s_('MattermostService|Fill in the word that works best for your team.')
%p
- Suggestions:
+ = s_('MattermostService|Suggestions:')
%code= 'gitlab'
%code= @project.path # Path contains no spaces, but dashes
%code= @project.full_path
.form-group
- = label_tag :request_url, 'Request URL', class: 'col-12 col-form-label label-bold'
+ = label_tag :request_url, s_('MattermostService|Request URL'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :request_url, service_trigger_url(subject), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#request_url', class: 'input-group-text')
.form-group
- = label_tag nil, 'Request method', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, s_('MattermostService|Request method'), class: 'col-12 col-form-label label-bold'
.col-12 POST
.form-group
- = label_tag :response_username, 'Response username', class: 'col-12 col-form-label label-bold'
+ = label_tag :response_username, s_('MattermostService|Response username'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :response_username, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#response_username', class: 'input-group-text')
.form-group
- = label_tag :response_icon, 'Response icon', class: 'col-12 col-form-label label-bold'
+ = label_tag :response_icon, s_('MattermostService|Response icon'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#response_icon', class: 'input-group-text')
.form-group
- = label_tag nil, 'Autocomplete', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Autocomplete'), class: 'col-12 col-form-label label-bold'
.col-12 Yes
.form-group
- = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-12 col-12 col-form-label label-bold'
+ = label_tag :autocomplete_hint, _('Autocomplete hint'), class: 'col-12 col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :autocomplete_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_hint', class: 'input-group-text')
.form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-12 col-form-label label-bold'
+ = label_tag :autocomplete_description, _('Autocomplete description'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
index f51dd581d29..cc005dd69b7 100644
--- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
@@ -3,14 +3,12 @@
.info-well
.well-segment
%p
- This service allows users to perform common operations on this
- project by entering slash commands in Mattermost.
+ = s_("MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost.")
= link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank' do
- View documentation
+ = _("View documentation")
= sprite_icon('external-link', size: 16)
%p.inline
- See list of available commands in Mattermost after setting up this service,
- by entering
+ = s_("MattermostService|See list of available commands in Mattermost after setting up this service, by entering")
%kbd.inline /&lt;trigger&gt; help
- unless enabled || @service.template?
= render 'projects/services/mattermost_slash_commands/detailed_help', subject: @service
diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
index 2da8e5428ec..aee81ea744a 100644
--- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
@@ -4,4 +4,4 @@
.col-sm-9.offset-sm-3
= link_to new_project_mattermost_path(@project), class: 'btn btn-lg' do
= custom_icon('mattermost_logo', size: 15)
- Add to Mattermost
+ = s_("MattermostService|Add to Mattermost")
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index 9b7732abc62..7f6717e298c 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -4,17 +4,15 @@
.info-well
.well-segment
%p
- This service allows users to perform common operations on this
- project by entering slash commands in Slack.
+ = s_("SlackService|This service allows users to perform common operations on this project by entering slash commands in Slack.")
= link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank' do
- View documentation
+ = _("View documentation")
= sprite_icon('external-link', size: 16)
%p.inline
- See list of available commands in Slack after setting up this service,
- by entering
+ = s_("SlackService|See list of available commands in Slack after setting up this service, by entering")
%kbd.inline /&lt;command&gt; help
- unless @service.template?
- %p To set up this service:
+ %p= _("To set up this service:")
%ul.list-unstyled.indent-list
%li
1.
@@ -27,11 +25,11 @@
.help-form
.form-group
- = label_tag nil, 'Command', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Command'), class: 'col-12 col-form-label label-bold'
.col-12
- %p Fill in the word that works best for your team.
+ %p= s_('SlackService|Fill in the word that works best for your team.')
%p
- Suggestions:
+ = _("Suggestions:")
%code= 'gitlab'
%code= @project.path # Path contains no spaces, but dashes
%code= @project.full_path
@@ -44,44 +42,44 @@
= clipboard_button(target: '#url', class: 'input-group-text')
.form-group
- = label_tag nil, 'Method', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Method'), class: 'col-12 col-form-label label-bold'
.col-12 POST
.form-group
- = label_tag :customize_name, 'Customize name', class: 'col-12 col-form-label label-bold'
+ = label_tag :customize_name, _('Customize name'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :customize_name, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#customize_name', class: 'input-group-text')
.form-group
- = label_tag nil, 'Customize icon', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Customize icon'), class: 'col-12 col-form-label label-bold'
.col-12
= image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36, class: 'mr-3')
- = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer')
+ = link_to(_('Download image'), asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer')
.form-group
- = label_tag nil, 'Autocomplete', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Autocomplete'), class: 'col-12 col-form-label label-bold'
.col-12 Show this command in the autocomplete list
.form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-12 col-form-label label-bold'
+ = label_tag :autocomplete_description, _('Autocomplete description'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_description', class: 'input-group-text')
.form-group
- = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-12 col-form-label label-bold'
+ = label_tag :autocomplete_usage_hint, _('Autocomplete usage hint'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_usage_hint', class: 'input-group-text')
.form-group
- = label_tag :descriptive_label, 'Descriptive label', class: 'col-12 col-form-label label-bold'
+ = label_tag :descriptive_label, _('Descriptive label'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
- = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control form-control-sm', readonly: 'readonly'
+ = text_field_tag :descriptive_label, _('Perform common operations on GitLab project'), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#descriptive_label', class: 'input-group-text')
@@ -89,12 +87,6 @@
%ul.list-unstyled.indent-list
%li
- 2. Paste the
- %strong Token
- into the field below
+ = s_("SlackService|2. Paste the <strong>Token</strong> into the field below").html_safe
%li
- 3. Select the
- %strong Active
- checkbox, press
- %strong Save changes
- and start using GitLab inside Slack!
+ = s_("SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!").html_safe
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 498a9744783..430d6071468 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -14,14 +14,14 @@
= f.label :build_allow_git_fetch_false, class: 'form-check-label' do
%strong git clone
%br
- %span.descr
+ %span
= _("Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job")
.form-check
= f.radio_button :build_allow_git_fetch, 'true', { class: 'form-check-input' }
= f.label :build_allow_git_fetch_true, class: 'form-check-label' do
%strong git fetch
%br
- %span.descr
+ %span
= _("Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)")
%hr
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index da48cb207a4..f495b4eaf30 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -6,7 +6,7 @@
= render 'shared/snippets/header'
.project-snippets
- %article.file-holder.snippet-file-content{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ %article.file-holder.snippet-file-content
= render 'shared/snippets/blob'
.row-content-block.top-block.content-component-block
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index c6197fe576e..51b7f2dd4b4 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -26,7 +26,7 @@
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
.prepend-top-default.append-bottom-default
- .md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ .md.md-file
= render_wiki_content(@page)
= render 'sidebar'
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 4f0be117035..93fc839a371 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -4,7 +4,7 @@
- @no_container = true
- @content_class = "issue-boards-content js-focus-mode-board"
- breadcrumb_title _("Issue Boards")
-- page_title _("Boards")
+- page_title("#{board.name}", _("Boards"))
- content_for :page_specific_javascripts do
diff --git a/app/views/shared/empty_states/_profile_tabs.html.haml b/app/views/shared/empty_states/_profile_tabs.html.haml
index 6da40e1b059..98a5a5953d0 100644
--- a/app/views/shared/empty_states/_profile_tabs.html.haml
+++ b/app/views/shared/empty_states/_profile_tabs.html.haml
@@ -12,7 +12,7 @@
%p= current_user_empty_message_description
- if secondary_button_link.present?
- = link_to secondary_button_label, secondary_button_link, class: 'btn btn-create btn-inverted'
+ = link_to secondary_button_label, secondary_button_link, class: 'btn btn-success btn-inverted'
= link_to primary_button_label, primary_button_link, class: 'btn btn-success'
- else
diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml
index 4f6a71b6071..875cacd1f4f 100644
--- a/app/views/shared/issuable/_close_reopen_button.html.haml
+++ b/app/views/shared/issuable/_close_reopen_button.html.haml
@@ -9,7 +9,7 @@
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
- if can_reopen
= link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
- class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
+ class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}", data: { qa_selector: 'reopen_issue_button' }
- else
- if can_update && !are_close_and_open_buttons_hidden
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 214e87052da..04a70e406ca 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -66,7 +66,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else
- if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
- = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.human_class_name} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped'
+ = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable], params: { destroy_confirm: true }), data: { confirm: "#{issuable.human_class_name} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
%span.append-right-10
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index 71123740ee4..93408e0bfc0 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -17,7 +17,7 @@
#{issuables_state_counter_text(type, :closed, display_count)}
- else
%li{ class: active_when(params[:state] == 'closed') }>
- = link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do
+ = link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed', qa_selector: 'closed_issues_link' } do
#{issuables_state_counter_text(type, :closed, display_count)}
= render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all, display_count)
diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml
index 559b5aa9c1e..24b4eae0c58 100644
--- a/app/views/shared/runners/_form.html.haml
+++ b/app/views/shared/runners/_form.html.haml
@@ -26,11 +26,6 @@
= f.check_box :locked, { class: 'form-check-input' }
%label.light{ for: :runner_locked }= _('When a runner is locked, it cannot be assigned to other projects')
.form-group.row
- = label_tag :token, class: 'col-form-label col-sm-2' do
- = _('Token')
- .col-sm-10
- = f.text_field :token, class: 'form-control', readonly: true
- .form-group.row
= label_tag :ip_address, class: 'col-form-label col-sm-2' do
= _('IP Address')
.col-sm-10
diff --git a/changelogs/unreleased/35060-remove-token-field.yml b/changelogs/unreleased/35060-remove-token-field.yml
new file mode 100644
index 00000000000..93a7b459dd8
--- /dev/null
+++ b/changelogs/unreleased/35060-remove-token-field.yml
@@ -0,0 +1,5 @@
+---
+title: Remove token field from runners edit form
+merge_request: 32231
+author:
+type: fixed
diff --git a/changelogs/unreleased/56130-operations-environments-shows-incorrect-deployment-date-for-manual-.yml b/changelogs/unreleased/56130-operations-environments-shows-incorrect-deployment-date-for-manual-.yml
new file mode 100644
index 00000000000..92f25ac07e2
--- /dev/null
+++ b/changelogs/unreleased/56130-operations-environments-shows-incorrect-deployment-date-for-manual-.yml
@@ -0,0 +1,6 @@
+---
+title: Update the timestamp in Operations > Environments to show correct deployment
+ date for manual deploy jobs
+merge_request: 32072
+author:
+type: fixed
diff --git a/changelogs/unreleased/57538-not-null-constraint-on-users-private-profile.yml b/changelogs/unreleased/57538-not-null-constraint-on-users-private-profile.yml
new file mode 100644
index 00000000000..bcbd8e3f11e
--- /dev/null
+++ b/changelogs/unreleased/57538-not-null-constraint-on-users-private-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Setting NOT NULL constraint to users.private_profile column
+merge_request: 14838
+author:
+type: other
diff --git a/changelogs/unreleased/62284-follow-up-from-resolve-api-to-get-all-project-group-members-returns-duplicates.yml b/changelogs/unreleased/62284-follow-up-from-resolve-api-to-get-all-project-group-members-returns-duplicates.yml
new file mode 100644
index 00000000000..0c73f73c297
--- /dev/null
+++ b/changelogs/unreleased/62284-follow-up-from-resolve-api-to-get-all-project-group-members-returns-duplicates.yml
@@ -0,0 +1,5 @@
+---
+title: Uses projects_authorizations.access_level in MembersFinder
+merge_request: 28887
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/62673-clean-note-app-tests.yml b/changelogs/unreleased/62673-clean-note-app-tests.yml
new file mode 100644
index 00000000000..abfcf8fe364
--- /dev/null
+++ b/changelogs/unreleased/62673-clean-note-app-tests.yml
@@ -0,0 +1,5 @@
+---
+title: make test of note app with comments disabled dry
+merge_request: 32383
+author: Romain Maneschi
+type: other
diff --git a/changelogs/unreleased/63262-notes-are-persisted-with-the-user-s-locale.yml b/changelogs/unreleased/63262-notes-are-persisted-with-the-user-s-locale.yml
new file mode 100644
index 00000000000..e55beb9db09
--- /dev/null
+++ b/changelogs/unreleased/63262-notes-are-persisted-with-the-user-s-locale.yml
@@ -0,0 +1,5 @@
+---
+title: Do not translate system notes into author's language
+merge_request: 32264
+author:
+type: fixed
diff --git a/changelogs/unreleased/64251-branch-name-set-cache.yml b/changelogs/unreleased/64251-branch-name-set-cache.yml
deleted file mode 100644
index 6ce4bdf5e43..00000000000
--- a/changelogs/unreleased/64251-branch-name-set-cache.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache branch and tag names as Redis sets
-merge_request: 30476
-author:
-type: performance
diff --git a/changelogs/unreleased/65063-Embeded-metrics-inconsistent-styles.yml b/changelogs/unreleased/65063-Embeded-metrics-inconsistent-styles.yml
new file mode 100644
index 00000000000..126cf29afc0
--- /dev/null
+++ b/changelogs/unreleased/65063-Embeded-metrics-inconsistent-styles.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed embeded metrics tooltip inconsistent styling
+merge_request: 31517
+author:
+type: fixed
diff --git a/changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml b/changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml
new file mode 100644
index 00000000000..a6a0ba4b4f4
--- /dev/null
+++ b/changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issue due notification emails not being threaded correctly
+merge_request: 32325
+author:
+type: fixed
diff --git a/changelogs/unreleased/bump-pages-1-8.yml b/changelogs/unreleased/bump-pages-1-8.yml
new file mode 100644
index 00000000000..0201ff0fd5f
--- /dev/null
+++ b/changelogs/unreleased/bump-pages-1-8.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade pages to 1.8.0
+merge_request: 32334
+author:
+type: other
diff --git a/changelogs/unreleased/ce-60465-prevent-comments-on-private-mrs.yml b/changelogs/unreleased/ce-60465-prevent-comments-on-private-mrs.yml
new file mode 100644
index 00000000000..ba970162447
--- /dev/null
+++ b/changelogs/unreleased/ce-60465-prevent-comments-on-private-mrs.yml
@@ -0,0 +1,3 @@
+---
+title: Ensure only authorised users can create notes on Merge Requests and Issues
+type: security
diff --git a/changelogs/unreleased/ce-slack-close-command.yml b/changelogs/unreleased/ce-slack-close-command.yml
new file mode 100644
index 00000000000..c20e623b117
--- /dev/null
+++ b/changelogs/unreleased/ce-slack-close-command.yml
@@ -0,0 +1,5 @@
+---
+title: Add a close issue slack slash command
+merge_request: 32150
+author:
+type: added
diff --git a/changelogs/unreleased/ce-xanf-move-auto-merge-failed-to-jest.yml b/changelogs/unreleased/ce-xanf-move-auto-merge-failed-to-jest.yml
new file mode 100644
index 00000000000..5a56a668c54
--- /dev/null
+++ b/changelogs/unreleased/ce-xanf-move-auto-merge-failed-to-jest.yml
@@ -0,0 +1,5 @@
+---
+title: Refactored Karma spec to Jest for mr_widget_auto_merge_failed
+merge_request: 32282
+author: Illya Klymov
+type: other
diff --git a/changelogs/unreleased/cluster_deployments.yml b/changelogs/unreleased/cluster_deployments.yml
new file mode 100644
index 00000000000..d854d16ea72
--- /dev/null
+++ b/changelogs/unreleased/cluster_deployments.yml
@@ -0,0 +1,5 @@
+---
+title: Add index to improve group cluster deployments query performance
+merge_request: 31988
+author:
+type: other
diff --git a/changelogs/unreleased/fix-create-milestone-btn-success.yml b/changelogs/unreleased/fix-create-milestone-btn-success.yml
new file mode 100644
index 00000000000..a1b1d305ce3
--- /dev/null
+++ b/changelogs/unreleased/fix-create-milestone-btn-success.yml
@@ -0,0 +1,5 @@
+---
+title: Fix style of secondary profile tab buttons.
+merge_request: 32010
+author: Wolfgang Faust
+type: fixed
diff --git a/changelogs/unreleased/fix-dropdown-closing.yml b/changelogs/unreleased/fix-dropdown-closing.yml
new file mode 100644
index 00000000000..5ce3a6b478e
--- /dev/null
+++ b/changelogs/unreleased/fix-dropdown-closing.yml
@@ -0,0 +1,5 @@
+---
+title: Fix dropdowns closing when click is released outside the dropdown
+merge_request: 32084
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-search-input-dropdown.yml b/changelogs/unreleased/fix-search-input-dropdown.yml
new file mode 100644
index 00000000000..a86f7eacfdb
--- /dev/null
+++ b/changelogs/unreleased/fix-search-input-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Fix top-nav search bar dropdown on xl displays
+merge_request: 31864
+author: Kemais Ehlers
+type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-13698-override-params.yml b/changelogs/unreleased/georgekoltsov-13698-override-params.yml
new file mode 100644
index 00000000000..1bbde160e18
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-13698-override-params.yml
@@ -0,0 +1,5 @@
+---
+title: Allow project feature permissions to be overridden during import with override_params
+merge_request: 32348
+author:
+type: fixed
diff --git a/changelogs/unreleased/handle-invalid-mirror-url.yml b/changelogs/unreleased/handle-invalid-mirror-url.yml
new file mode 100644
index 00000000000..c72707a2cad
--- /dev/null
+++ b/changelogs/unreleased/handle-invalid-mirror-url.yml
@@ -0,0 +1,5 @@
+---
+title: Handle invalid mirror url
+merge_request: 32353
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/id-optimize-sql-requests-mr-show.yml b/changelogs/unreleased/id-optimize-sql-requests-mr-show.yml
new file mode 100644
index 00000000000..8b171a96316
--- /dev/null
+++ b/changelogs/unreleased/id-optimize-sql-requests-mr-show.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce the number of SQL requests on MR-show
+merge_request: 32192
+author:
+type: performance
diff --git a/changelogs/unreleased/issue_40630.yml b/changelogs/unreleased/issue_40630.yml
new file mode 100644
index 00000000000..61cc773d5a9
--- /dev/null
+++ b/changelogs/unreleased/issue_40630.yml
@@ -0,0 +1,5 @@
+---
+title: Save collapsed option for board lists in database
+merge_request: 31069
+author:
+type: added
diff --git a/changelogs/unreleased/new-project-milestone-primary-button.yml b/changelogs/unreleased/new-project-milestone-primary-button.yml
new file mode 100644
index 00000000000..ac0305a2e21
--- /dev/null
+++ b/changelogs/unreleased/new-project-milestone-primary-button.yml
@@ -0,0 +1,5 @@
+---
+title: New project milestone primary button
+merge_request: 32355
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/runner-chart-repo-use-new-location.yml b/changelogs/unreleased/runner-chart-repo-use-new-location.yml
new file mode 100644
index 00000000000..790e5704c04
--- /dev/null
+++ b/changelogs/unreleased/runner-chart-repo-use-new-location.yml
@@ -0,0 +1,5 @@
+---
+title: Use new location for gitlab-runner helm charts
+merge_request: 32384
+author:
+type: other
diff --git a/changelogs/unreleased/security-59549-add-capcha-for-failed-logins.yml b/changelogs/unreleased/security-59549-add-capcha-for-failed-logins.yml
new file mode 100644
index 00000000000..55f9e36c39c
--- /dev/null
+++ b/changelogs/unreleased/security-59549-add-capcha-for-failed-logins.yml
@@ -0,0 +1,5 @@
+---
+title: Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-61974-limit-issue-comment-size-2.yml b/changelogs/unreleased/security-61974-limit-issue-comment-size-2.yml
new file mode 100644
index 00000000000..962171dc6f8
--- /dev/null
+++ b/changelogs/unreleased/security-61974-limit-issue-comment-size-2.yml
@@ -0,0 +1,5 @@
+---
+title: Speed up regexp in namespace format by failing fast after reaching maximum namespace depth
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-61974-limit-issue-comment-size.yml b/changelogs/unreleased/security-61974-limit-issue-comment-size.yml
new file mode 100644
index 00000000000..6d5ef057d83
--- /dev/null
+++ b/changelogs/unreleased/security-61974-limit-issue-comment-size.yml
@@ -0,0 +1,5 @@
+---
+title: Limit the size of issuable description and comments
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-64711-fix-commit-todos.yml b/changelogs/unreleased/security-64711-fix-commit-todos.yml
new file mode 100644
index 00000000000..ce4b3cdeeaf
--- /dev/null
+++ b/changelogs/unreleased/security-64711-fix-commit-todos.yml
@@ -0,0 +1,5 @@
+---
+title: Send TODOs for comments on commits correctly
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-ci-metrics-permissions.yml b/changelogs/unreleased/security-ci-metrics-permissions.yml
new file mode 100644
index 00000000000..51c6493442a
--- /dev/null
+++ b/changelogs/unreleased/security-ci-metrics-permissions.yml
@@ -0,0 +1,6 @@
+---
+title: Restrict MergeRequests#test_reports to authenticated users with read-access
+ on Builds
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-enable-image-proxy.yml b/changelogs/unreleased/security-enable-image-proxy.yml
new file mode 100644
index 00000000000..88b49ffd9e8
--- /dev/null
+++ b/changelogs/unreleased/security-enable-image-proxy.yml
@@ -0,0 +1,5 @@
+---
+title: Added image proxy to mitigate potential stealing of IP addresses
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-epic-notes-api-reveals-historical-info-ce-master.yml b/changelogs/unreleased/security-epic-notes-api-reveals-historical-info-ce-master.yml
new file mode 100644
index 00000000000..c639098721e
--- /dev/null
+++ b/changelogs/unreleased/security-epic-notes-api-reveals-historical-info-ce-master.yml
@@ -0,0 +1,5 @@
+---
+title: Filter out old system notes for epics in notes api endpoint response
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-exposed-default-branch.yml b/changelogs/unreleased/security-exposed-default-branch.yml
new file mode 100644
index 00000000000..bf32617ee8a
--- /dev/null
+++ b/changelogs/unreleased/security-exposed-default-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid exposing unaccessible repo data upon GFM post processing
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-html-injection-for-label-description-ce-master.yml b/changelogs/unreleased/security-fix-html-injection-for-label-description-ce-master.yml
new file mode 100644
index 00000000000..07124ac399b
--- /dev/null
+++ b/changelogs/unreleased/security-fix-html-injection-for-label-description-ce-master.yml
@@ -0,0 +1,5 @@
+---
+title: Fix HTML injection for label description
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-markdown-xss.yml b/changelogs/unreleased/security-fix-markdown-xss.yml
new file mode 100644
index 00000000000..7ef19f13fd5
--- /dev/null
+++ b/changelogs/unreleased/security-fix-markdown-xss.yml
@@ -0,0 +1,5 @@
+---
+title: Make sure HTML text is always escaped when replacing label/milestone references.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix_jira_ssrf_vulnerability.yml b/changelogs/unreleased/security-fix_jira_ssrf_vulnerability.yml
new file mode 100644
index 00000000000..25518dd2d05
--- /dev/null
+++ b/changelogs/unreleased/security-fix_jira_ssrf_vulnerability.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent DNS rebind on JIRA service integration
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-gitaly-1-61-0.yml b/changelogs/unreleased/security-gitaly-1-61-0.yml
new file mode 100644
index 00000000000..cbcd5f545a0
--- /dev/null
+++ b/changelogs/unreleased/security-gitaly-1-61-0.yml
@@ -0,0 +1,5 @@
+---
+title: "Gitaly: ignore git redirects"
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-group-runners-permissions.yml b/changelogs/unreleased/security-group-runners-permissions.yml
new file mode 100644
index 00000000000..6c74be30b6d
--- /dev/null
+++ b/changelogs/unreleased/security-group-runners-permissions.yml
@@ -0,0 +1,5 @@
+---
+title: Use admin_group authorization in Groups::RunnersController
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-hide_merge_request_ids_on_emails.yml b/changelogs/unreleased/security-hide_merge_request_ids_on_emails.yml
new file mode 100644
index 00000000000..cd8c9590a70
--- /dev/null
+++ b/changelogs/unreleased/security-hide_merge_request_ids_on_emails.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent disclosure of merge request ID via email
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-id-filter-timeline-activities-for-guests.yml b/changelogs/unreleased/security-id-filter-timeline-activities-for-guests.yml
new file mode 100644
index 00000000000..0fa5f89e2c0
--- /dev/null
+++ b/changelogs/unreleased/security-id-filter-timeline-activities-for-guests.yml
@@ -0,0 +1,5 @@
+---
+title: Show cross-referenced MR-id in issues' activities only to authorized users
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-katex-dos-master.yml b/changelogs/unreleased/security-katex-dos-master.yml
new file mode 100644
index 00000000000..df803a5eafd
--- /dev/null
+++ b/changelogs/unreleased/security-katex-dos-master.yml
@@ -0,0 +1,5 @@
+---
+title: Enforce max chars and max render time in markdown math
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-mr-head-pipeline-leak.yml b/changelogs/unreleased/security-mr-head-pipeline-leak.yml
new file mode 100644
index 00000000000..b15b353ff41
--- /dev/null
+++ b/changelogs/unreleased/security-mr-head-pipeline-leak.yml
@@ -0,0 +1,5 @@
+---
+title: Check permissions before responding in MergeController#pipeline_status
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-personal-snippets.yml b/changelogs/unreleased/security-personal-snippets.yml
new file mode 100644
index 00000000000..95f61993b98
--- /dev/null
+++ b/changelogs/unreleased/security-personal-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Remove EXIF from users/personal snippet uploads.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-project-import-bypass.yml b/changelogs/unreleased/security-project-import-bypass.yml
new file mode 100644
index 00000000000..fc7b823509c
--- /dev/null
+++ b/changelogs/unreleased/security-project-import-bypass.yml
@@ -0,0 +1,5 @@
+---
+title: Fix project import restricted visibility bypass via API
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-sarcila-fix-weak-session-management.yml b/changelogs/unreleased/security-sarcila-fix-weak-session-management.yml
new file mode 100644
index 00000000000..a37a3099519
--- /dev/null
+++ b/changelogs/unreleased/security-sarcila-fix-weak-session-management.yml
@@ -0,0 +1,6 @@
+---
+title: Fix weak session management by clearing password reset tokens after login (username/email)
+ are updated
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-ssrf-kubernetes-dns.yml b/changelogs/unreleased/security-ssrf-kubernetes-dns.yml
new file mode 100644
index 00000000000..4d6335e4b08
--- /dev/null
+++ b/changelogs/unreleased/security-ssrf-kubernetes-dns.yml
@@ -0,0 +1,5 @@
+---
+title: Fix SSRF via DNS rebinding in Kubernetes Integration
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-add-delete-confirmation.yml b/changelogs/unreleased/sh-add-delete-confirmation.yml
new file mode 100644
index 00000000000..21c383183e7
--- /dev/null
+++ b/changelogs/unreleased/sh-add-delete-confirmation.yml
@@ -0,0 +1,5 @@
+---
+title: Make it harder to delete issuables accidentally
+merge_request: 32376
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-move-api.yml b/changelogs/unreleased/sh-fix-issue-move-api.yml
new file mode 100644
index 00000000000..e98d04b02d9
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-move-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix moving issues API failing when text includes commit URLs
+merge_request: 32317
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-snippet-visibility-api.yml b/changelogs/unreleased/sh-fix-snippet-visibility-api.yml
new file mode 100644
index 00000000000..5cfb9cdedc0
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-snippet-visibility-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix snippets API not working with visibility level
+merge_request: 32286
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-lfs-object-batches.yml b/changelogs/unreleased/sh-lfs-object-batches.yml
new file mode 100644
index 00000000000..09043e286be
--- /dev/null
+++ b/changelogs/unreleased/sh-lfs-object-batches.yml
@@ -0,0 +1,5 @@
+---
+title: Makes LFS object linker process OIDs in batches
+merge_request: 32268
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-support-content-for-snippets-api.yml b/changelogs/unreleased/sh-support-content-for-snippets-api.yml
new file mode 100644
index 00000000000..60a5d98da46
--- /dev/null
+++ b/changelogs/unreleased/sh-support-content-for-snippets-api.yml
@@ -0,0 +1,5 @@
+---
+title: Standardize use of `content` parameter in snippets API
+merge_request: 32296
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-upgrade-mermaid-8-2-4.yml b/changelogs/unreleased/sh-upgrade-mermaid-8-2-4.yml
new file mode 100644
index 00000000000..bdb64e43ecf
--- /dev/null
+++ b/changelogs/unreleased/sh-upgrade-mermaid-8-2-4.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Mermaid to v8.2.4
+merge_request: 32186
+author:
+type: fixed
diff --git a/changelogs/unreleased/ss-add-board-name-to-page-title.yml b/changelogs/unreleased/ss-add-board-name-to-page-title.yml
new file mode 100644
index 00000000000..8a04972fa32
--- /dev/null
+++ b/changelogs/unreleased/ss-add-board-name-to-page-title.yml
@@ -0,0 +1,5 @@
+---
+title: Added board name to page title in boards view
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/tc-cleanup-issue-created-text-mail.yml b/changelogs/unreleased/tc-cleanup-issue-created-text-mail.yml
new file mode 100644
index 00000000000..cc590ee38eb
--- /dev/null
+++ b/changelogs/unreleased/tc-cleanup-issue-created-text-mail.yml
@@ -0,0 +1,5 @@
+---
+title: Bring text mail for new issue & MR more in line
+merge_request: 32254
+author:
+type: changed
diff --git a/changelogs/unreleased/todos-include-issue-mr-titles.yml b/changelogs/unreleased/todos-include-issue-mr-titles.yml
new file mode 100644
index 00000000000..a9fb9116070
--- /dev/null
+++ b/changelogs/unreleased/todos-include-issue-mr-titles.yml
@@ -0,0 +1,5 @@
+---
+title: Add Issue and Merge Request titles to Todo items
+merge_request: 30435
+author: Arun Kumar Mohan
+type: changed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-8-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-8-0.yml
new file mode 100644
index 00000000000..719a273b30c
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-8-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.8.0
+merge_request: 32289
+author:
+type: other
diff --git a/config/application.rb b/config/application.rb
index 294ed470298..81889561473 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -165,7 +165,6 @@ module Gitlab
config.assets.precompile << "locale/**/app.js"
config.assets.precompile << "emoji_sprites.css"
config.assets.precompile << "errors.css"
- config.assets.precompile << "csslab.css"
config.assets.precompile << "highlight/themes/*.css"
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 81433b620bc..4160f488a7a 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -515,7 +515,7 @@ Settings['sidekiq']['log_format'] ||= 'default'
Settings['gitlab_shell'] ||= Settingslogic.new({})
Settings.gitlab_shell['path'] = Settings.absolute(Settings.gitlab_shell['path'] || Settings.gitlab['user_home'] + '/gitlab-shell/')
Settings.gitlab_shell['hooks_path'] = :deprecated_use_gitlab_shell_path_instead
-Settings.gitlab_shell['authorized_keys_file'] ||= nil
+Settings.gitlab_shell['authorized_keys_file'] ||= File.join(Dir.home, '.ssh', 'authorized_keys')
Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret')
Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil?
Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil?
diff --git a/config/initializers/asset_proxy_settings.rb b/config/initializers/asset_proxy_settings.rb
new file mode 100644
index 00000000000..92247aba1b8
--- /dev/null
+++ b/config/initializers/asset_proxy_settings.rb
@@ -0,0 +1,6 @@
+#
+# Asset proxy settings
+#
+ActiveSupport.on_load(:active_record) do
+ Banzai::Filter::AssetProxyFilter.initialize_settings
+end
diff --git a/config/initializers/fill_shards.rb b/config/initializers/fill_shards.rb
index 18e067c8854..cad662e12f3 100644
--- a/config/initializers/fill_shards.rb
+++ b/config/initializers/fill_shards.rb
@@ -1,3 +1,5 @@
-if Shard.connected? && !Gitlab::Database.read_only?
+# The `table_exists?` check is needed because during our migration rollback testing,
+# `Shard.connected?` could be cached and return true even though the table doesn't exist
+if Shard.connected? && Shard.table_exists? && !Gitlab::Database.read_only?
Shard.populate!
end
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index f9055285e5c..a3810be70b2 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -1,6 +1,7 @@
require 'peek/adapters/redis'
Peek::Adapters::Redis.prepend ::Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled
+Peek.singleton_class.prepend ::Gitlab::PerformanceBar::WithTopLevelWarnings
Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) }
diff --git a/config/initializers/rest-client-hostname_override.rb b/config/initializers/rest-client-hostname_override.rb
new file mode 100644
index 00000000000..80b123ebe61
--- /dev/null
+++ b/config/initializers/rest-client-hostname_override.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module RestClient
+ class Request
+ attr_accessor :hostname_override
+
+ module UrlBlocker
+ def transmit(uri, req, payload, &block)
+ begin
+ ip, hostname_override = Gitlab::UrlBlocker.validate!(uri, allow_local_network: allow_settings_local_requests?,
+ allow_localhost: allow_settings_local_requests?,
+ dns_rebind_protection: dns_rebind_protection?)
+
+ self.hostname_override = hostname_override
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ raise ArgumentError, "URL '#{uri}' is blocked: #{e.message}"
+ end
+
+ # Gitlab::UrlBlocker returns a Addressable::URI which we need to coerce
+ # to URI so that rest-client can use it to determine if it's a
+ # URI::HTTPS or not. It uses it to set `net.use_ssl` to true or not:
+ #
+ # https://github.com/rest-client/rest-client/blob/f450a0f086f1cd1049abbef2a2c66166a1a9ba71/lib/restclient/request.rb#L656
+ ip_as_uri = URI.parse(ip)
+ super(ip_as_uri, req, payload, &block)
+ end
+
+ def net_http_object(hostname, port)
+ super.tap do |http|
+ http.hostname_override = hostname_override if hostname_override
+ end
+ end
+
+ private
+
+ def dns_rebind_protection?
+ return false if Gitlab.http_proxy_env?
+
+ Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
+ end
+
+ def allow_settings_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
+ end
+
+ prepend UrlBlocker
+ end
+end
diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb
index 1d2bb2bce0a..d8a4da8cdf9 100644
--- a/config/initializers/warden.rb
+++ b/config/initializers/warden.rb
@@ -19,6 +19,7 @@ Rails.application.configure do |config|
Warden::Manager.after_authentication(scope: :user) do |user, auth, opts|
ActiveSession.cleanup(user)
+ Gitlab::AnonymousSession.new(auth.request.remote_ip, session_id: auth.request.session.id).cleanup_session_per_ip_entries
end
Warden::Manager.after_set_user(scope: :user, only: :fetch) do |user, auth, opts|
diff --git a/config/routes.rb b/config/routes.rb
index d633228a495..c333550f758 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -110,6 +110,7 @@ Rails.application.routes.draw do
draw :smartcard
draw :jira_connect
draw :username
+ draw :trial_registration
end
Gitlab.ee do
diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb
index 920f8454ce2..096ef146e07 100644
--- a/config/routes/uploads.rb
+++ b/config/routes/uploads.rb
@@ -30,6 +30,10 @@ scope path: :uploads do
to: 'uploads#create',
constraints: { model: /personal_snippet|user/, id: /\d+/ },
as: 'upload'
+
+ post ':model/authorize',
+ to: 'uploads#authorize',
+ constraints: { model: /personal_snippet|user/ }
end
# Redirect old note attachments path to new uploads path.
diff --git a/db/migrate/20190219201635_add_asset_proxy_settings.rb b/db/migrate/20190219201635_add_asset_proxy_settings.rb
new file mode 100644
index 00000000000..9de38cf8a89
--- /dev/null
+++ b/db/migrate/20190219201635_add_asset_proxy_settings.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddAssetProxySettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :asset_proxy_enabled, :boolean, default: false, null: false
+ add_column :application_settings, :asset_proxy_url, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :application_settings, :asset_proxy_whitelist, :text
+ add_column :application_settings, :encrypted_asset_proxy_secret_key, :text
+ add_column :application_settings, :encrypted_asset_proxy_secret_key_iv, :string # rubocop:disable Migration/AddLimitToStringColumns
+ end
+end
diff --git a/db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb b/db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb
new file mode 100644
index 00000000000..4561e1e8aa9
--- /dev/null
+++ b/db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLoginRecaptchaProtectionEnabledToApplicationSettings < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :login_recaptcha_protection_enabled, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20190816151221_add_active_jobs_limit_to_plans.rb b/db/migrate/20190816151221_add_active_jobs_limit_to_plans.rb
new file mode 100644
index 00000000000..951ff41f1a8
--- /dev/null
+++ b/db/migrate/20190816151221_add_active_jobs_limit_to_plans.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddActiveJobsLimitToPlans < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :plans, :active_jobs_limit, :integer, default: 0
+ end
+
+ def down
+ remove_column :plans, :active_jobs_limit
+ end
+end
diff --git a/db/migrate/20190819131155_add_cluster_status_index_to_deployments.rb b/db/migrate/20190819131155_add_cluster_status_index_to_deployments.rb
new file mode 100644
index 00000000000..bfa91e33558
--- /dev/null
+++ b/db/migrate/20190819131155_add_cluster_status_index_to_deployments.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddClusterStatusIndexToDeployments < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :deployments, [:cluster_id, :status]
+ end
+
+ def down
+ remove_concurrent_index :deployments, [:cluster_id, :status]
+ end
+end
diff --git a/db/migrate/20190822181528_create_list_user_preferences.rb b/db/migrate/20190822181528_create_list_user_preferences.rb
new file mode 100644
index 00000000000..a7993818b50
--- /dev/null
+++ b/db/migrate/20190822181528_create_list_user_preferences.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateListUserPreferences < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :list_user_preferences do |t|
+ t.references :user, index: true, null: false, foreign_key: { on_delete: :cascade }
+ t.references :list, index: true, null: false, foreign_key: { on_delete: :cascade }
+ t.timestamps_with_timezone null: false
+ t.boolean :collapsed
+ end
+
+ add_index :list_user_preferences, [:user_id, :list_id], unique: true
+ end
+end
diff --git a/db/migrate/20190826090628_remove_redundant_deployments_index.rb b/db/migrate/20190826090628_remove_redundant_deployments_index.rb
new file mode 100644
index 00000000000..6b009c17d64
--- /dev/null
+++ b/db/migrate/20190826090628_remove_redundant_deployments_index.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveRedundantDeploymentsIndex < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index :deployments, :cluster_id
+ end
+
+ def down
+ add_concurrent_index :deployments, :cluster_id
+ end
+end
diff --git a/db/migrate/20190828083843_add_index_to_ci_job_artifacts_on_project_id_for_security_reports.rb b/db/migrate/20190828083843_add_index_to_ci_job_artifacts_on_project_id_for_security_reports.rb
new file mode 100644
index 00000000000..5253f25aab4
--- /dev/null
+++ b/db/migrate/20190828083843_add_index_to_ci_job_artifacts_on_project_id_for_security_reports.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddIndexToCiJobArtifactsOnProjectIdForSecurityReports < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_job_artifacts,
+ :project_id,
+ name: "index_ci_job_artifacts_on_project_id_for_security_reports",
+ where: "file_type IN (5, 6, 7, 8)"
+ end
+
+ def down
+ remove_concurrent_index :ci_job_artifacts,
+ :project_id,
+ name: "index_ci_job_artifacts_on_project_id_for_security_reports"
+ end
+end
diff --git a/db/post_migrate/20190725080128_set_not_null_on_users_private_profile.rb b/db/post_migrate/20190725080128_set_not_null_on_users_private_profile.rb
new file mode 100644
index 00000000000..db42e949d3f
--- /dev/null
+++ b/db/post_migrate/20190725080128_set_not_null_on_users_private_profile.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class SetNotNullOnUsersPrivateProfile < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ Gitlab::BackgroundMigration.steal('MigrateNullPrivateProfileToFalse')
+
+ # rubocop:disable Migration/UpdateLargeTable
+ # rubocop:disable Migration/UpdateColumnInBatches
+ # Data has been migrated previously, count should be close to 0
+ update_column_in_batches(:users, :private_profile, false) do |table, query|
+ query.where(table[:private_profile].eq(nil))
+ end
+
+ change_column_null :users, :private_profile, false
+ end
+
+ def down
+ change_column_null :users, :private_profile, true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f30dad3d030..13572022235 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_08_20_163320) do
+ActiveRecord::Schema.define(version: 2019_08_28_083843) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -272,12 +272,18 @@ ActiveRecord::Schema.define(version: 2019_08_20_163320) do
t.boolean "lock_memberships_to_ldap", default: false, null: false
t.boolean "time_tracking_limit_to_hours", default: false, null: false
t.string "grafana_url", default: "/-/grafana", null: false
+ t.boolean "login_recaptcha_protection_enabled", default: false, null: false
t.string "outbound_local_requests_whitelist", limit: 255, default: [], null: false, array: true
t.integer "raw_blob_request_limit", default: 300, null: false
t.boolean "allow_local_requests_from_web_hooks_and_services", default: false, null: false
t.boolean "allow_local_requests_from_system_hooks", default: true, null: false
t.bigint "instance_administration_project_id"
t.string "snowplow_collector_hostname"
+ t.boolean "asset_proxy_enabled", default: false, null: false
+ t.string "asset_proxy_url"
+ t.text "asset_proxy_whitelist"
+ t.text "encrypted_asset_proxy_secret_key"
+ t.string "encrypted_asset_proxy_secret_key_iv"
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
@@ -658,6 +664,7 @@ ActiveRecord::Schema.define(version: 2019_08_20_163320) do
t.index ["file_store"], name: "index_ci_job_artifacts_on_file_store"
t.index ["job_id", "file_type"], name: "index_ci_job_artifacts_on_job_id_and_file_type", unique: true
t.index ["project_id"], name: "index_ci_job_artifacts_on_project_id"
+ t.index ["project_id"], name: "index_ci_job_artifacts_on_project_id_for_security_reports", where: "(file_type = ANY (ARRAY[5, 6, 7, 8]))"
end
create_table "ci_job_variables", force: :cascade do |t|
@@ -1145,7 +1152,7 @@ ActiveRecord::Schema.define(version: 2019_08_20_163320) do
t.integer "status", limit: 2, null: false
t.datetime_with_timezone "finished_at"
t.integer "cluster_id"
- t.index ["cluster_id"], name: "index_deployments_on_cluster_id"
+ t.index ["cluster_id", "status"], name: "index_deployments_on_cluster_id_and_status"
t.index ["created_at"], name: "index_deployments_on_created_at"
t.index ["deployable_type", "deployable_id"], name: "index_deployments_on_deployable_type_and_deployable_id"
t.index ["environment_id", "id"], name: "index_deployments_on_environment_id_and_id"
@@ -1921,6 +1928,17 @@ ActiveRecord::Schema.define(version: 2019_08_20_163320) do
t.datetime "updated_at"
end
+ create_table "list_user_preferences", force: :cascade do |t|
+ t.bigint "user_id", null: false
+ t.bigint "list_id", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.boolean "collapsed"
+ t.index ["list_id"], name: "index_list_user_preferences_on_list_id"
+ t.index ["user_id", "list_id"], name: "index_list_user_preferences_on_user_id_and_list_id", unique: true
+ t.index ["user_id"], name: "index_list_user_preferences_on_user_id"
+ end
+
create_table "lists", id: :serial, force: :cascade do |t|
t.integer "board_id", null: false
t.integer "label_id"
@@ -2502,6 +2520,7 @@ ActiveRecord::Schema.define(version: 2019_08_20_163320) do
t.string "title"
t.integer "active_pipelines_limit"
t.integer "pipeline_size_limit"
+ t.integer "active_jobs_limit", default: 0
t.index ["name"], name: "index_plans_on_name"
end
@@ -3499,7 +3518,7 @@ ActiveRecord::Schema.define(version: 2019_08_20_163320) do
t.integer "theme_id", limit: 2
t.integer "accepted_term_id"
t.string "feed_token"
- t.boolean "private_profile", default: false
+ t.boolean "private_profile", default: false, null: false
t.boolean "include_private_contributions"
t.string "commit_email"
t.boolean "auditor", default: false, null: false
@@ -3879,6 +3898,8 @@ ActiveRecord::Schema.define(version: 2019_08_20_163320) do
add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade
add_foreign_key "lfs_file_locks", "projects", on_delete: :cascade
add_foreign_key "lfs_file_locks", "users", on_delete: :cascade
+ add_foreign_key "list_user_preferences", "lists", on_delete: :cascade
+ add_foreign_key "list_user_preferences", "users", on_delete: :cascade
add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade
add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade
add_foreign_key "lists", "milestones", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index f12c06199c2..9a0252cc334 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -358,7 +358,7 @@ The following documentation relates to the DevOps **Secure** stage:
| [Dependency Scanning](user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](user/application_security/dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
| [Group Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. |
-| [License Compliance](user/application_security/license_management/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
+| [License Compliance](user/application_security/license_compliance/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
| [Project Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View the latest security reports for your project. |
| [Static Application Security Testing (SAST)](user/application_security/sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. |
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
index 86dd398343b..7c14d4004db 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
@@ -32,7 +32,7 @@ For example, [Active Directory](https://technet.microsoft.com/en-us/library/hh83
We won't cover the installation and configuration of Windows Server or Active Directory Domain Services in this tutorial. There are a number of resources online to guide you through this process:
-- Install Windows Server 2012 - (_technet.microsoft.com_) - [Installing Windows Server 2012 ](https://technet.microsoft.com/en-us/library/jj134246(v=ws.11).aspx)
+- Install Windows Server 2012 - (_technet.microsoft.com_) - [Installing Windows Server 2012](https://technet.microsoft.com/en-us/library/jj134246(v=ws.11).aspx)
- Install Active Directory Domain Services (AD DS) (_technet.microsoft.com_)- [Install Active Directory Domain Services](https://technet.microsoft.com/windows-server-docs/identity/ad-ds/deploy/install-active-directory-domain-services--level-100-#BKMK_PS)
@@ -249,7 +249,7 @@ After configuring LDAP, basic authentication will be available. Users can then l
Users that are removed from the LDAP base group (e.g `OU=GitLab INT,DC=GitLab,DC=org`) will be **blocked** in GitLab. [More information](../ldap.md#security) on LDAP security.
-If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `` jon.doe@example.com `` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
+If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `jon.doe@example.com` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
## LDAP extended features on GitLab EE
diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md
index 5524c3ba092..41745e8caae 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -98,20 +98,20 @@ Now that the Okta app is configured, it's time to enable it in GitLab.
>**Notes:**
>
>- Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
- of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
- installation to generate the correct value).
+ > of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
+ > installation to generate the correct value).
>
>- To get the `idp_cert_fingerprint` fingerprint, first download the
- certificate from the Okta app you registered and then run:
- `openssl x509 -in okta.cert -noout -fingerprint`. Substitute `okta.cert`
- with the location of your certificate.
+ > certificate from the Okta app you registered and then run:
+ > `openssl x509 -in okta.cert -noout -fingerprint`. Substitute `okta.cert`
+ > with the location of your certificate.
>
>- Change the value of `idp_sso_target_url`, with the value of the
- **Identity Provider Single Sign-On URL** from the step when you
- configured the Okta app.
+ > **Identity Provider Single Sign-On URL** from the step when you
+ > configured the Okta app.
>
>- Change the value of `issuer` to the value of the **Audience Restriction** from your Okta app configuration. This will identify GitLab
- to the IdP.
+ > to the IdP.
>
>- Leave `name_identifier_format` as-is.
diff --git a/doc/administration/geo/disaster_recovery/index.md b/doc/administration/geo/disaster_recovery/index.md
index ba95843b0b0..407539885a6 100644
--- a/doc/administration/geo/disaster_recovery/index.md
+++ b/doc/administration/geo/disaster_recovery/index.md
@@ -297,7 +297,7 @@ for another **primary** node. All the old replication settings will be overwritt
## Troubleshooting
-### I followed the disaster recovery instructions and now two-factor auth is broken!
+### I followed the disaster recovery instructions and now two-factor auth is broken
The setup instructions for Geo prior to 10.5 failed to replicate the
`otp_key_base` secret, which is used to encrypt the two-factor authentication
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index fe1557fd8b5..3ae92b07736 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -538,7 +538,7 @@ can simply reset the existing tracking database with:
sudo gitlab-rake geo:db:reset
```
-### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node.
+### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node
This error refers to a problem with the database replica on a **secondary** node,
which Geo expects to have access to. It usually means, either:
@@ -552,7 +552,7 @@ PostgreSQL instances:
- A read-only replica of the **primary** node.
- A regular, writable instance that holds replication metadata. That is, the Geo tracking database.
-### Geo node does not appear to be replicating the database from the primary node.
+### Geo node does not appear to be replicating the database from the primary node
The most common problems that prevent the database from replicating correctly are:
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index daec5f14e36..eab4b2c6eea 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -185,7 +185,7 @@ Check the directory layout on your Gitaly server to be sure.
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:
-
+
For `gitaly1.internal`:
```
@@ -194,7 +194,7 @@ Check the directory layout on your Gitaly server to be sure.
{ 'name' => 'storage1' },
]
```
-
+
For `gitaly2.internal`:
```
@@ -226,7 +226,7 @@ Check the directory layout on your Gitaly server to be sure.
```
1. Append the following to `/home/git/gitaly/config.toml` for each respective server:
-
+
For `gitaly1.internal`:
```toml
@@ -236,7 +236,7 @@ Check the directory layout on your Gitaly server to be sure.
[[storage]]
name = 'storage1'
```
-
+
For `gitaly2.internal`:
```toml
@@ -262,12 +262,12 @@ Additionally, you need to
[disable Rugged if previously manually enabled](../high_availability/nfs.md#improving-nfs-performance-with-gitlab).
We assume that your `gitaly1.internal` Gitaly server can be reached at
-`gitaly1.internal:8075` from your GitLab server, and that Gitaly server
+`gitaly1.internal:8075` from your GitLab server, and that Gitaly server
can read and write to `/mnt/gitlab/default` and `/mnt/gitlab/storage1`.
We assume also that your `gitaly2.internal` Gitaly server can be reached at
-`gitaly2.internal:8075` from your GitLab server, and that Gitaly server
-can read and write to `/mnt/gitlab/storage2`.
+`gitaly2.internal:8075` from your GitLab server, and that Gitaly server
+can read and write to `/mnt/gitlab/storage2`.
**For Omnibus GitLab**
@@ -540,6 +540,7 @@ gitaly['concurrency'] = [
}
]
```
+
This will limit the number of in-flight RPC calls for the given RPC's.
The limit is applied per repository. In the example above, each on the
Gitaly server can have at most 20 simultaneous PostUploadPack calls in
@@ -729,4 +730,4 @@ To remove the proxy setting, run the following commands (depending on which vari
```bash
unset http_proxy
unset https_proxy
-``` \ No newline at end of file
+```
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 8818a9606de..0d1dd06871a 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -4,9 +4,9 @@ type: reference
# Configuring GitLab for Scaling and High Availability
-> **Note:** There is some additional configuration near the bottom for
- additional GitLab application servers. It's important to read and understand
- these additional steps before proceeding with GitLab installation.
+NOTE: **Note:** There is some additional configuration near the bottom for
+additional GitLab application servers. It's important to read and understand
+these additional steps before proceeding with GitLab installation.
1. If necessary, install the NFS client utility packages using the following
commands:
@@ -82,19 +82,19 @@ type: reference
1. [Enable monitoring](#enable-monitoring)
- > **Note:** To maintain uniformity of links across HA clusters, the `external_url`
+ NOTE: **Note:** To maintain uniformity of links across HA clusters, the `external_url`
on the first application server as well as the additional application
servers should point to the external url that users will use to access GitLab.
In a typical HA setup, this will be the url of the load balancer which will
route traffic to all GitLab application servers in the HA cluster.
- >
- > **Note:** When you specify `https` in the `external_url`, as in the example
+
+ NOTE: **Note:** When you specify `https` in the `external_url`, as in the example
above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
certificates are not present, Nginx will fail to start. See
[Nginx documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
for more information.
- >
- > **Note:** It is best to set the `uid` and `gid`s prior to the initial reconfigure
+
+ NOTE: **Note:** It is best to set the `uid` and `gid`s prior to the initial reconfigure
of GitLab. Omnibus will not recursively `chown` directories if set after the initial reconfigure.
## First GitLab application server
@@ -105,8 +105,8 @@ Do not run this on additional application servers.
1. Initialize the database by running `sudo gitlab-rake gitlab:setup`.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
-> **WARNING:** Only run this setup task on **NEW** GitLab instances because it
- will wipe any existing data.
+ CAUTION: **WARNING:** Only run this setup task on **NEW** GitLab instances because it
+ will wipe any existing data.
## Extra configuration for additional GitLab application servers
@@ -173,9 +173,10 @@ If you enable Monitoring, it must be enabled on **all** GitLab servers.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
-> **Warning:** After changing `unicorn['listen']` in `gitlab.rb`, and running `sudo gitlab-ctl reconfigure`,
- it can take an extended period of time for unicorn to complete reloading after receiving a `HUP`.
- For more information, see the [issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4401).
+ CAUTION: **Warning:**
+ After changing `unicorn['listen']` in `gitlab.rb`, and running `sudo gitlab-ctl reconfigure`,
+ it can take an extended period of time for unicorn to complete reloading after receiving a `HUP`.
+ For more information, see the [issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4401).
## Troubleshooting
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index 16a193550a1..df6c554decb 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -1,7 +1,6 @@
# PlantUML & GitLab
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8537) in
-> GitLab 8.16.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8537) in GitLab 8.16.
When [PlantUML](http://plantuml.com) integration is enabled and configured in
GitLab we are able to create simple diagrams in AsciiDoc and Markdown documents
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 9030c941cad..9b1efb610f8 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -338,3 +338,12 @@ installations from source.
[repocheck]: repository_checks.md
[Rack Attack]: ../security/rack_attack.md
[Rate Limit]: ../user/admin_area/settings/rate_limits_on_raw_endpoints.md
+
+## `database_load_balancing.log`
+
+Introduced in GitLab 12.3 for observability of [Database Load
+Balancing](https://docs.gitlab.com/ee/administration/database_load_balancing.html)
+when enabled. This file lives in
+`/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab
+packages or in `/home/git/gitlab/log/database_load_balancing.log` for
+installations from source.
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 4809bee0df6..ca48326c6d0 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -161,7 +161,16 @@ the `monitoring.sidekiq_exporter` configuration option in `gitlab.yml`.
| `geo_repositories_retrying_verification_count` | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | url |
| `geo_wikis_retrying_verification_count` | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | url |
-### Ruby metrics
+## Database load balancing metrics **(PREMIUM ONLY)**
+
+The following metrics are available:
+
+| Metric | Type | Since | Description |
+|:--------------------------------- |:--------- |:------------------------------------------------------------- |:-------------------------------------- |
+| `db_load_balancing_hosts` | Gauge | [12.3](https://gitlab.com/gitlab-org/gitlab-ee/issues/13630) | Current number of load balancing hosts |
+| `db_load_balancing_index` | Gauge | [12.3](https://gitlab.com/gitlab-org/gitlab-ee/issues/13630) | Current load balancing host index |
+
+## Ruby metrics
Some basic Ruby runtime metrics are available:
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index c77a1a9638f..fdfde22647d 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -343,21 +343,6 @@ world. Custom domains and TLS are supported.
1. Restart NGINX
1. [Restart GitLab][restart]
-## Change storage path
-
-Follow the steps below to change the default path where GitLab Pages' contents
-are stored.
-
-1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`.
- If you wish to store them in another location you must set it up in
- `/etc/gitlab/gitlab.rb`:
-
- ```ruby
- gitlab_rails['pages_path'] = "/mnt/storage/pages"
- ```
-
-1. [Reconfigure GitLab][reconfigure]
-
## NGINX caveats
>**Note:**
@@ -468,7 +453,6 @@ than GitLab to prevent XSS attacks.
[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx
[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md
[pages-userguide]: ../../user/project/pages/index.md
-[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../restart_gitlab.md#installations-from-source
[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.4.0
[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example
diff --git a/doc/administration/raketasks/github_import.md b/doc/administration/raketasks/github_import.md
index ccd9c0afb1d..f8eecc97c33 100644
--- a/doc/administration/raketasks/github_import.md
+++ b/doc/administration/raketasks/github_import.md
@@ -1,6 +1,6 @@
# GitHub import
-> [Introduced]( https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10308) in GitLab 9.1.
+> [Introduced]( https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10308) in GitLab 9.1.
In order to retrieve and import GitHub repositories, you will need a
[GitHub personal access token](https://github.com/settings/tokens).
diff --git a/doc/administration/raketasks/ldap.md b/doc/administration/raketasks/ldap.md
index e880d76e756..d0ebe272b6d 100644
--- a/doc/administration/raketasks/ldap.md
+++ b/doc/administration/raketasks/ldap.md
@@ -19,8 +19,6 @@ sudo gitlab-rake gitlab:ldap:check
sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
```
-------
-
By default, the task will return a sample of 100 LDAP users. Change this
limit by passing a number to the check task:
@@ -135,8 +133,6 @@ What is the old provider? Ex. 'ldapmain': ldapmain
What is the new provider? Ex. 'ldapcustom': ldapmycompany
```
-------
-
This tasks also accepts the `force` environment variable which will skip the
confirmation dialog:
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index 8d0b5b42515..89335fcd2a8 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -260,7 +260,7 @@ To check the status of migrations, you can use the following rake task:
sudo gitlab-rake db:migrate:status
```
-This will output a table with a `Status` of `up` or `down` for
+This will output a table with a `Status` of `up` or `down` for
each Migration ID.
```bash
@@ -269,4 +269,4 @@ database: gitlabhq_production
Status Migration ID Migration Name
--------------------------------------------------
up migration_id migration_name
-``` \ No newline at end of file
+```
diff --git a/doc/administration/raketasks/uploads/migrate.md b/doc/administration/raketasks/uploads/migrate.md
index 86e8b763f51..d9b4c9b3369 100644
--- a/doc/administration/raketasks/uploads/migrate.md
+++ b/doc/administration/raketasks/uploads/migrate.md
@@ -108,7 +108,7 @@ sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeReque
> Introduced in GitLab 12.3.
-To migrate all uploads created by legacy uploaders, run:
+To migrate all uploads created by legacy uploaders, run:
```shell
bundle exec rake gitlab:uploads:legacy:migrate
diff --git a/doc/administration/raketasks/uploads/sanitize.md b/doc/administration/raketasks/uploads/sanitize.md
index ae5ccfb9e37..7574660d848 100644
--- a/doc/administration/raketasks/uploads/sanitize.md
+++ b/doc/administration/raketasks/uploads/sanitize.md
@@ -37,6 +37,8 @@ Parameter | Type | Description
`stop_id` | integer | Only uploads with equal or smaller ID will be processed
`dry_run` | boolean | Do not remove EXIF data, only check if EXIF data are present or not, default: true
`sleep_time` | float | Pause for number of seconds after processing each image, default: 0.3 seconds
+`uploader` | string | Run sanitization only for uploads of the given uploader (`FileUploader`, `PersonalFileUploader`, `NamespaceFileUploader`)
+`since` | date | Run sanitization only for uploads newer than given date (e.g. `2019-05-01`)
If you have too many uploads, you can speed up sanitization by setting
`sleep_time` to a lower value or by running multiple rake tasks in parallel,
diff --git a/doc/administration/troubleshooting/elasticsearch.md b/doc/administration/troubleshooting/elasticsearch.md
index c4a7ba01fae..13b9c30b29d 100644
--- a/doc/administration/troubleshooting/elasticsearch.md
+++ b/doc/administration/troubleshooting/elasticsearch.md
@@ -266,9 +266,9 @@ ElasticSearch administrator.
Generally speaking, ensure:
-* The ElasticSearch server **is not** running on the same node as GitLab.
-* The ElasticSearch server have enough RAM and CPU cores.
-* That sharding **is** being used.
+- The ElasticSearch server **is not** running on the same node as GitLab.
+- The ElasticSearch server have enough RAM and CPU cores.
+- That sharding **is** being used.
Going into some more detail here, if ElasticSearch is running on the same server as GitLab, resource contention is **very** likely to occur. Ideally, ElasticSearch, which requires ample resources, should be running on its own server (maybe coupled with logstash and kibana).
diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
index 9b016c64e29..c41edb5dbfc 100644
--- a/doc/administration/troubleshooting/sidekiq.md
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -96,8 +96,9 @@ corresponding Ruby code where this is happening.
`gdb` can be another effective tool for debugging Sidekiq. It gives you a little
more interactive way to look at each thread and see what's causing problems.
-> **Note:** Attaching to a process with `gdb` will suspends the normal operation
- of the process (Sidekiq will not process jobs while `gdb` is attached).
+NOTE: **Note:**
+Attaching to a process with `gdb` will suspends the normal operation
+of the process (Sidekiq will not process jobs while `gdb` is attached).
Start by attaching to the Sidekiq PID:
@@ -280,10 +281,10 @@ has number of drawbacks, as mentioned in [Why Ruby’s Timeout is dangerous (and
> This is where the implications get interesting, and terrifying. This means that an exception can get raised:
>
-> * during a network request (ok, as long as the surrounding code is prepared to catch Timeout::Error)
-> * during the cleanup for the network request
-> * during a rescue block
-> * while creating an object to save to the database afterwards
-> * in any of your code, regardless of whether it could have possibly raised an exception before
+> - during a network request (ok, as long as the surrounding code is prepared to catch Timeout::Error)
+> - during the cleanup for the network request
+> - during a rescue block
+> - while creating an object to save to the database afterwards
+> - in any of your code, regardless of whether it could have possibly raised an exception before
>
> Nobody writes code to defend against an exception being raised on literally any line. That’s not even possible. So Thread.raise is basically like a sneak attack on your code that could result in almost anything. It would probably be okay if it were pure-functional code that did not modify any state. But this is Ruby, so that’s unlikely :)
diff --git a/doc/api/README.md b/doc/api/README.md
index 33394d46a2d..036b46da6e5 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -396,7 +396,7 @@ GET /api/v4/projects/diaspora%2Fdiaspora
NOTE: **Note:**
A project's **path** is not necessarily the same as its **name**. A
-project's path can found in the project's URL or in the project's settings
+project's path can be found in the project's URL or in the project's settings
under **General > Advanced > Change path**.
## Branches and tags name encoding
@@ -422,7 +422,7 @@ We can call the API with `array` and `hash` types parameters as shown below:
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
-d "import_sources[]=github" \
-d "import_sources[]=bitbucket" \
-"https://gitlab.example.com/api/v4/some_endpoint
+https://gitlab.example.com/api/v4/some_endpoint
```
### `hash`
diff --git a/doc/api/epics.md b/doc/api/epics.md
index 87ae0c48199..08eb84bfb63 100644
--- a/doc/api/epics.md
+++ b/doc/api/epics.md
@@ -165,7 +165,7 @@ POST /groups/:id/epics
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `title` | string | yes | The title of the epic |
| `labels` | string | no | The comma separated list of labels |
-| `description` | string | no | The description of the epic |
+| `description` | string | no | The description of the epic. Limited to 1 000 000 characters. |
| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
| `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) |
@@ -231,7 +231,7 @@ PUT /groups/:id/epics/:epic_iid
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `epic_iid` | integer/string | yes | The internal ID of the epic |
| `title` | string | no | The title of an epic |
-| `description` | string | no | The description of an epic |
+| `description` | string | no | The description of an epic. Limited to 1 000 000 characters. |
| `labels` | string | no | The comma separated list of labels |
| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d99a4c37d72..e87270f884a 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -516,4 +516,3 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `username` | String! | |
| `avatarUrl` | String! | |
| `webUrl` | String! | |
-
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 0d500f783aa..d7f5b1b463b 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -158,6 +158,7 @@ Parameters:
| `with_shared` | boolean | no | Include projects shared to this group. Default is `true` |
| `include_subgroups` | boolean | no | Include projects in subgroups of this group. Default is `false` |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
+| `with_security_reports` | boolean | no | **(ULTIMATE)** Return only projects that have security reports artifacts present in any of their builds. This means "projects with security reports enabled". Default is `false` |
Example response:
diff --git a/doc/api/issues.md b/doc/api/issues.md
index cadc9291489..7498d2d840b 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -590,7 +590,7 @@ POST /projects/:id/issues
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iid` | integer/string | no | The internal ID of the project's issue (requires admin or project owner rights) |
| `title` | string | yes | The title of an issue |
-| `description` | string | no | The description of an issue |
+| `description` | string | no | The description of an issue. Limited to 1 000 000 characters. |
| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
| `assignee_ids` | integer array | no | The ID of a user to assign issue |
| `milestone_id` | integer | no | The global ID of a milestone to assign issue |
@@ -691,7 +691,7 @@ PUT /projects/:id/issues/:issue_iid
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
| `title` | string | no | The title of an issue |
-| `description` | string | no | The description of an issue |
+| `description` | string | no | The description of an issue. Limited to 1 000 000 characters. |
| `confidential` | boolean | no | Updates an issue to be confidential |
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
| `milestone_id` | integer | no | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 49ed4968b0d..0d030ef30c8 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -837,7 +837,7 @@ POST /projects/:id/merge_requests
| `title` | string | yes | Title of MR |
| `assignee_id` | integer | no | Assignee user ID |
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
-| `description` | string | no | Description of MR |
+| `description` | string | no | Description of MR. Limited to 1 000 000 characters. |
| `target_project_id` | integer | no | The target project (numeric id) |
| `labels` | string | no | Labels for MR as a comma-separated list |
| `milestone_id` | integer | no | The global ID of a milestone |
@@ -990,7 +990,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
| `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
| `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. |
-| `description` | string | no | Description of MR |
+| `description` | string | no | Description of MR. Limited to 1 000 000 characters. |
| `state_event` | string | no | New state (close/reopen) |
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
| `squash` | boolean | no | Squash commits into a single commit when merging |
diff --git a/doc/api/notes.md b/doc/api/notes.md
index acbf0334563..1f5baf7d0e1 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -113,7 +113,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `issue_iid` (required) - The IID of an issue
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights)
```bash
@@ -133,7 +133,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `issue_iid` (required) - The IID of an issue
- `note_id` (required) - The ID of a note
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
@@ -231,7 +231,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `snippet_id` (required) - The ID of a snippet
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
```bash
@@ -251,7 +251,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `snippet_id` (required) - The ID of a snippet
- `note_id` (required) - The ID of a note
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes?body=note
@@ -354,7 +354,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `merge_request_iid` (required) - The IID of a merge request
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
### Modify existing merge request note
@@ -370,7 +370,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `merge_request_iid` (required) - The IID of a merge request
- `note_id` (required) - The ID of a note
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes?body=note
@@ -472,7 +472,7 @@ Parameters:
| --------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `epic_id` | integer | yes | The ID of an epic |
-| `body` | string | yes | The content of a note |
+| `body` | string | yes | The content of a note. Limited to 1 000 000 characters. |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
@@ -493,7 +493,7 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `epic_id` | integer | yes | The ID of an epic |
| `note_id` | integer | yes | The ID of a note |
-| `body` | string | yes | The content of a note |
+| `body` | string | yes | The content of a note. Limited to 1 000 000 characters. |
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 710b63c9a2f..e3d8fe68e08 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -68,6 +68,9 @@ Example response:
"allow_local_requests_from_hooks_and_services": true,
"allow_local_requests_from_web_hooks_and_services": true,
"allow_local_requests_from_system_hooks": false
+ "asset_proxy_enabled": true,
+ "asset_proxy_url": "https://assets.example.com",
+ "asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"]
}
```
@@ -141,6 +144,9 @@ Example response:
"user_show_add_ssh_key_message": true,
"file_template_project_id": 1,
"local_markdown_version": 0,
+ "asset_proxy_enabled": true,
+ "asset_proxy_url": "https://assets.example.com",
+ "asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"],
"geo_node_allowed_ips": "0.0.0.0/0, ::/0",
"allow_local_requests_from_hooks_and_services": true,
"allow_local_requests_from_web_hooks_and_services": true,
@@ -186,6 +192,10 @@ are listed in the descriptions of the relevant settings.
| `allow_local_requests_from_hooks_and_services` | boolean | no | (Deprecated: Use `allow_local_requests_from_web_hooks_and_services` instead) Allow requests to the local network from hooks and services. |
| `allow_local_requests_from_web_hooks_and_services` | boolean | no | Allow requests to the local network from web hooks and services. |
| `allow_local_requests_from_system_hooks` | boolean | no | Allow requests to the local network from system hooks. |
+| `asset_proxy_enabled` | boolean | no | (**If enabled, requires:** `asset_proxy_url`) Enable proxying of assets. GitLab restart is required to apply changes. |
+| `asset_proxy_secret_key` | string | no | Shared secret with the asset proxy server. GitLab restart is required to apply changes. |
+| `asset_proxy_url` | string | no | URL of the asset proxy server. GitLab restart is required to apply changes. |
+| `asset_proxy_whitelist` | string or array of strings | no | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. GitLab restart is required to apply changes. |
| `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
| `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. |
| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. |
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 4be13204227..90d0e6a7dc6 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -131,7 +131,7 @@ Its feature set is listed on the table below according to DevOps stages.
| **Secure** ||
| [Container Scanning](../user/application_security/container_scanning/index.md) **(ULTIMATE)** | Check your Docker containers for known vulnerabilities.|
| [Dependency Scanning](../user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
-| [License Compliance](../user/application_security/license_management/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. |
+| [License Compliance](../user/application_security/license_compliance/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. |
| [Security Test reports](../user/project/merge_requests/index.md#security-reports-ultimate) **(ULTIMATE)** | Check for app vulnerabilities. |
## Examples
diff --git a/doc/ci/directed_acyclic_graph/index.md b/doc/ci/directed_acyclic_graph/index.md
index 1c38c08b7cb..60e3120ba33 100644
--- a/doc/ci/directed_acyclic_graph/index.md
+++ b/doc/ci/directed_acyclic_graph/index.md
@@ -36,7 +36,7 @@ It has a pipeline that looks like the following:
| ----- | ---- | ------ |
| build_a | test_a | deploy_a |
| build_b | test_b | deploy_b |
-| build_c | test_c | deploy_c |
+| build_c | test_c | deploy_c |
| build_d | test_d | deploy_d |
Using a DAG, you can relate the `_a` jobs to each other separately from the `_b` jobs,
diff --git a/doc/ci/examples/license_management.md b/doc/ci/examples/license_management.md
index 53e38111bf3..0d12c9a20f2 100644
--- a/doc/ci/examples/license_management.md
+++ b/doc/ci/examples/license_management.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../../user/application_security/license_management/index.md'
+redirect_to: '../../user/application_security/license_compliance/index.md'
---
-This document was moved to [another location](../../user/application_security/license_management/index.md).
+This document was moved to [another location](../../user/application_security/license_compliance/index.md).
diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md
index f8a3fab88e3..ace1204511e 100644
--- a/doc/ci/jenkins/index.md
+++ b/doc/ci/jenkins/index.md
@@ -32,7 +32,7 @@ There are some high level differences between the products worth mentioning:
## Groovy vs. YAML
-Jenkins Pipelines are based on [Groovy](https://groovy-lang.org/), so the pipeline specification is written as code.
+Jenkins Pipelines are based on [Groovy](https://groovy-lang.org/), so the pipeline specification is written as code.
GitLab works a bit differently, we use the more highly structured [YAML](https://yaml.org/) format, which
places scripting elements inside of `script:` blocks separate from the pipeline specification itself.
@@ -56,7 +56,7 @@ rspec:
- .in-docker
script:
- rake rspec
-```
+```
## Artifact publishing
@@ -143,7 +143,7 @@ default:
GitLab CI also lets you define stages, but is a little bit more free-form to configure. The GitLab [`stages` keyword](../yaml/README.md#stages)
is a top level setting that enumerates the list of stages, but you are not required to nest individual jobs underneath
-the `stages` section. Any job defined in the `.gitlab-ci.yml` can be made a part of any stage through use of the
+the `stages` section. Any job defined in the `.gitlab-ci.yml` can be made a part of any stage through use of the
[`stage:` keyword](../yaml/README.md#stage).
Note that, unless otherwise specified, every pipeline is instantiated with a `build`, `test`, and `deploy` stage
@@ -229,4 +229,4 @@ our very powerful [`only/except` rules system](../yaml/README.md#onlyexcept-basi
```yaml
my_job:
only: branches
-``` \ No newline at end of file
+```
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
index 80a1c264bc4..7998b0452be 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
@@ -141,4 +141,4 @@ please ask administrator to execute the following commands:
> sudo gitlab-rails console # Login to Rails console of GitLab instance.
> Feature.enabled?(:merge_trains_enabled) # Check if it's enabled or not.
> Feature.disable(:merge_trains_enabled) # Disable the feature flag.
-``` \ No newline at end of file
+```
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 269bd5c3428..abb503c6516 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -156,8 +156,7 @@ An admin can enable/disable a specific Runner for projects:
## Protected Runners
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13194)
-> in GitLab 10.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13194) in GitLab 10.0.
You can protect Runners from revealing sensitive information.
Whenever a Runner is protected, the Runner picks only jobs created on
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
index 9ea113969c8..ce69a7df885 100644
--- a/doc/ci/services/mysql.md
+++ b/doc/ci/services/mysql.md
@@ -27,8 +27,8 @@ variables:
NOTE: **Note:**
The `MYSQL_DATABASE` and `MYSQL_ROOT_PASSWORD` variables can't be set in the GitLab UI.
-To set them, assign them to a variable [in the UI](../variables/README.md#via-the-ui),
-and then assign that variable to the
+To set them, assign them to a variable [in the UI](../variables/README.md#via-the-ui),
+and then assign that variable to the
`MYSQL_DATABASE` and `MYSQL_ROOT_PASSWORD` variables in your `.gitlab-ci.yml`.
And then configure your application to use the database, for example:
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 2a382f18038..f62a4660713 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -58,8 +58,7 @@ Read more about the [pipelines trigger API][trigapi].
#### When a pipeline depends on the artifacts of another pipeline **(PREMIUM)**
-> The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
- in [GitLab Premium][ee] 9.5.
+> The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346] in [GitLab Premium][ee] 9.5.
With the introduction of dependencies between different projects, one of
them may need to access artifacts created by a previous one. This process
@@ -271,7 +270,7 @@ Old triggers, created before GitLab 9.0 will be marked as legacy.
Triggers with the legacy label do not have an associated user and only have
access to the current project. They are considered deprecated and will be
-removed with one of the future versions of GitLab.
+removed with one of the future versions of GitLab.
[ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017
[ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 1368764bcf8..7a60dedc206 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1699,7 +1699,7 @@ dashboards.
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
-The `license_management` report collects [Licenses](../../user/project/merge_requests/license_management.md)
+The `license_management` report collects [Licenses](../../user/application_security/license_compliance/index.md)
as artifacts.
The collected License Compliance report will be uploaded to GitLab as an artifact and will
@@ -1899,9 +1899,8 @@ job1:
### `retry`
-> [Introduced][ce-12909] in GitLab 9.5.
-> [Behaviour expanded](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21758)
-> in GitLab 11.5 to control on which failures to retry.
+> - [Introduced][ce-12909] in GitLab 9.5.
+> - [Behaviour expanded](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21758) in GitLab 11.5 to control on which failures to retry.
`retry` allows you to configure how many times a job is going to be retried in
case of a failure.
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 3637a08c3cd..887f17b05b8 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -65,7 +65,7 @@ Sign up for the mailing list, answer GitLab questions on StackOverflow or
respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
the remaining issues on the GitHub issue tracker.
-## I want to contribute!
+## I want to contribute
If you want to contribute to GitLab,
[issues with the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index c9ae00d148a..edd83f67d3b 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -88,7 +88,7 @@ in an EE MR. To pass the test, simply remove the docs changes from the EE MR, an
## Changing document location
Changing a document's location requires specific steps to be followed to ensure that
-users can seamlessly access the new doc page, whether they are accesing content
+users can seamlessly access the new doc page, whether they are accessing content
on a GitLab instance domain at `/help` or at docs.gitlab.com. Be sure to ping a
GitLab technical writer if you have any questions during the process (such as
whether the move is necessary), and ensure that a technical writer reviews this
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index 025a946da0e..158e69df2a6 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -13,7 +13,7 @@ and the section on Content in the [Style Guide](styleguide.md).
## Components of a documentation page
-Most pages will be dedicated to a specifig GitLab feature or to a use case that involves
+Most pages will be dedicated to a specific GitLab feature or to a use case that involves
one or more features, potentially in conjunction with third-party tools.
Every feature or use case document should include the following content in the following sequence,
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index c1e3eb9680b..283e8bea8d5 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -506,15 +506,6 @@ Example:
For more information, see the [confidential issue](../../user/project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab-ce/issues/<issue_number>`.
```
-### Unlinking emails
-
-By default, all email addresses will render in an email tag on docs.gitlab.com.
-To escape the code block and unlink email addresses, use two backticks:
-
-```md
-`` example@email.com ``
-```
-
## Navigation
To indicate the steps of navigation through the UI:
@@ -783,8 +774,6 @@ For multiple paragraphs, use the symbol `>` before every line:
>
> - This is a list item
> - Second item in the list
->
-> ### This is an `h3`
```
Which renders to:
@@ -795,9 +784,6 @@ Which renders to:
>
> - This is a list item
> - Second item in the list
->
-> ### This is an `h3`
->{:.no_toc}
## Terms
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 1358851f3cd..391361a4b8f 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -1047,8 +1047,6 @@ code base. Examples of backports include the following:
Here is a workflow to make sure those changes end up backported safely into CE too.
-(This approach does not refer to changes introduced via [csslab](https://gitlab.com/gitlab-org/csslab/).)
-
1. **Make your changes in the EE branch.** If possible, keep a separated commit (to be squashed) to help backporting and review.
1. **Open merge request to EE project.**
1. **Apply the changes you made to CE files in a branch of the CE project.** (Tip: Use `patch` with the diff from your commit in EE branch)
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 676bce32998..3a8ea04407f 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -81,7 +81,7 @@ bundle and included on the page.
> can find this out by inspecting `document.body.dataset.page` within your
> browser's developer console while on any page within gitlab.
-#### Important Considerations:
+#### Important Considerations
- **Keep Entry Points Lite:**
Page-specific javascript entry points should be as lite as possible. These
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index 83444093f9c..2df0e846671 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -94,7 +94,7 @@ become available, you will be able to share job templates like this
Dependencies should be kept to the minimum. The introduction of a new
dependency should be argued in the merge request, as per our [Approval
Guidelines](../code_review.md#approval-guidelines). Both [License
-Management](../../user/project/merge_requests/license_management.md)
+Management](../../user/application_security/license_compliance/index.md)
**(ULTIMATE)** and [Dependency
Scanning](../../user/application_security/dependency_scanning/index.md)
**(ULTIMATE)** should be activated on all projects to ensure new dependencies
diff --git a/doc/development/i18n/translation.md b/doc/development/i18n/translation.md
index 62be3786549..15b1af1aa8f 100644
--- a/doc/development/i18n/translation.md
+++ b/doc/development/i18n/translation.md
@@ -92,4 +92,3 @@ To propose additions to the glossary please
In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036068906&categorieLien=id)).
So, to include both genders, write “Utilisateurs et utilisatrices” instead of “Utilisateur·rice·s”.
When space is missing, the male gender should be used alone.
-
diff --git a/doc/development/new_fe_guide/dependencies.md b/doc/development/new_fe_guide/dependencies.md
index 8a6930acd37..161ffb1fb57 100644
--- a/doc/development/new_fe_guide/dependencies.md
+++ b/doc/development/new_fe_guide/dependencies.md
@@ -1,6 +1,6 @@
# Dependencies
-## Adding Dependencies.
+## Adding Dependencies
GitLab uses `yarn` to manage dependencies. These dependencies are defined in
two groups within `package.json`, `dependencies` and `devDependencies`. For
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index e0d413b748b..b990425ca3c 100644
--- a/doc/development/new_fe_guide/development/testing.md
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -3,4 +3,3 @@ redirect_to: '../../testing_guide/frontend_testing.md'
---
This document was moved to [another location](../../testing_guide/frontend_testing.md).
-
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index f30a83a4c71..0f982c3a48b 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -13,7 +13,7 @@ a level that is difficult to manage.
Test heuristics can help solve this problem. They concisely address many of the common ways bugs
manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform
our test design. We can find some helpful heuristics documented in the Handbook in the
-[Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section.
+[Test Engineering](https://about.gitlab.com/handbook/engineering/quality/test-engineering/#test-heuristics) section.
## Test speed
diff --git a/doc/development/testing_guide/end_to_end/quick_start_guide.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md
index e1df8be8b6f..d52d6db38b9 100644
--- a/doc/development/testing_guide/end_to_end/quick_start_guide.md
+++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md
@@ -10,7 +10,7 @@ It's important to understand that end-to-end tests of isolated features, such as
If you don't exactly understand what we mean by **not everything needs to happen through the GUI,** please make sure you've read the [best practices](best_practices.md) before moving on.
-## This document covers the following items:
+## This document covers the following items
- [0.](#0-are-end-to-end-tests-needed) Identifying if end-to-end tests are really needed
- [1.](#1-identifying-the-devops-stage) Identifying the [DevOps stage](https://about.gitlab.com/stages-devops-lifecycle/) of the feature that you are going to cover with end-to-end tests
diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md
index 97560e616a1..54ed3f34c89 100644
--- a/doc/development/testing_guide/end_to_end/style_guide.md
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -141,4 +141,4 @@ Resource::MergeRequest.fabricate! do |merge_request_page|
end
```
-> Besides the advantage of having a standard in place, by following this standard we also write shorter lines of code. \ No newline at end of file
+> Besides the advantage of having a standard in place, by following this standard we also write shorter lines of code.
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index 96e8c30a679..173471e3af8 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -13,7 +13,7 @@ importance.
GitLab is built on top of [Ruby on Rails](https://rubyonrails.org/), and we're using [RSpec] for all
the backend tests, with [Capybara] for end-to-end integration testing.
-On the frontend side, we're using [Karma] and [Jasmine] for JavaScript unit and
+On the frontend side, we're using [Jest](https://jestjs.io/) and [Karma](http://karma-runner.github.io/)/[Jasmine](https://jasmine.github.io/) for JavaScript unit and
integration testing.
Following are two great articles that everyone should read to understand what
@@ -64,6 +64,4 @@ Everything you should know about how to run end-to-end tests using
[RSpec]: https://github.com/rspec/rspec-rails#feature-specs
[Capybara]: https://github.com/teamcapybara/capybara
-[Karma]: http://karma-runner.github.io/
-[Jasmine]: https://jasmine.github.io/
[gitlab-qa]: https://gitlab.com/gitlab-org/gitlab-qa
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 11449712a04..28a60660995 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -132,7 +132,7 @@ to prevent other pods from being scheduled on this node pool.
This is to ensure Tiller isn't affected by "noisy" neighbors that could put
their node under pressure.
-## How to:
+## How to
### Log into my Review App
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index 0090c84cbf0..1aee306f492 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -126,7 +126,7 @@ possible).
| ---------- | -------------- | ----- |
| `spec/features/` | [Capybara] + [RSpec] | If your test has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. |
-### Consider **not** writing a system test!
+### Consider **not** writing a system test
If we're confident that the low-level components work well (and we should be if
we have enough Unit & Integration tests), we shouldn't need to duplicate their
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index a998ab74a96..0f7a24042bb 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://design.gitlab.com/product-foundations/motion'
+redirect_to: 'https://design.gitlab.com/product-foundations/motion/'
---
-The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/motion).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/motion/).
diff --git a/doc/development/ux_guide/illustrations.md b/doc/development/ux_guide/illustrations.md
index 3592d25c95d..815f870f8c5 100644
--- a/doc/development/ux_guide/illustrations.md
+++ b/doc/development/ux_guide/illustrations.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://design.gitlab.com/product-foundations/illustration'
+redirect_to: 'https://design.gitlab.com/product-foundations/illustration/'
---
-The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/illustration).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/illustration/).
diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md
index 1a6a26152fa..28f32fefb95 100644
--- a/doc/gitlab-basics/add-merge-request.md
+++ b/doc/gitlab-basics/add-merge-request.md
@@ -12,8 +12,6 @@ check the [merge requests documentation](../user/project/merge_requests/index.md
you can watch our [GitLab Flow video](https://www.youtube.com/watch?v=InKNIvky2KE) for
a quick overview of working with merge requests.
----
-
1. Before you start, you should have already [created a branch](create-branch.md)
and [pushed your changes](start-using-git.md#send-changes-to-gitlabcom) to GitLab.
1. Go to the project where you'd like to merge your changes and click on the
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 8bbaf5d1927..18565daa900 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -2,7 +2,7 @@
type: howto
---
-# Creating projects
+# Create a project
Most work in GitLab is done within a [Project](../user/project/index.md). Files and
code are saved in projects, and most features are used within the scope of projects.
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index 338b96374aa..98f2679c9d6 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -5,7 +5,7 @@ type: howto
# Create and add your SSH public key
It is best practice to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
-In order to use SSH, you will need to
+In order to use SSH, you will need to:
1. [Create an SSH key pair](#creating-your-ssh-key-pair) on your local computer.
1. [Add the key to GitLab](#adding-your-ssh-public-key-to-gitlab).
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index c5939dc6856..dfb1dbcf1ed 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -179,7 +179,7 @@ to make sure your VM is configured to use a _static_ public IP address (i.e. not
or you will have to reconfigure the DNS `A` record each time Azure reassigns your VM a new public IP
address. Read [IP address types and allocation methods in Azure][Azure-IP-Address-Types] to learn more.
-## Let's open some ports!
+## Let's open some ports
At this stage you should have a running and fully operational VM. However, none of the services on
your VM (e.g. GitLab) will be publicly accessible via the internet until you have opened up the
@@ -333,6 +333,7 @@ If you're running Windows, you'll need to connect using [PuTTY] or an equivalent
If you're running Linux or macOS, then you already have an SSH client installed.
> **Note:**
+>
> - Remember that you will need to login with the username and password you specified
> [when you created](#basics) your Azure VM
> - If you need to reset your VM password, read
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 2989a4edaf7..6039ddc45ae 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -611,7 +611,7 @@ You can specify a different Git repository by providing it as an extra parameter
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
```
-Next, make sure gitaly configured:
+Next, make sure that Gitaly is configured:
```sh
# Restrict Gitaly socket access
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index 71ea2e25533..86a66dc4569 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -15,6 +15,7 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name help` | Shows all available slash commands |
| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` |
| `/project-name issue show <id>` | Shows the issue with id `<id>` |
+| `/project-name issue close <id>` | Closes the issue with id `<id>` |
| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
| `/project-name issue move <id> to <project>` | Moves issue ID `<id>` to `<project>` |
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md
index 8c1f4a126b1..c8782a2cfc2 100644
--- a/doc/legal/corporate_contributor_license_agreement.md
+++ b/doc/legal/corporate_contributor_license_agreement.md
@@ -16,7 +16,7 @@ You accept and agree to the following terms and conditions for Your present and
Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
- You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of (name of Your corporation here)." Such designations of exclusion for unauthorized employees are to be submitted via email to legal@gitlab.com. It is Your responsibility to notify GitLab B.V. when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf. Such notification should also be sent via email to legal@gitlab.com.
+ You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of (name of Your corporation here)." Such designations of exclusion for unauthorized employees are to be submitted via email to `legal@gitlab.com`. It is Your responsibility to notify GitLab B.V. when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf. Such notification should also be sent via email to `legal@gitlab.com`.
- **Contributions:**
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 2b8379141c3..50e491f29a2 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -32,7 +32,7 @@ upgrade to 8.0 until you finish the migration procedure.
## Before upgrading
-If you have GitLab CI installed using omnibus-gitlab packages but **you don't want to migrate your existing data**:
+If you have GitLab CI installed using Omnibus GitLab packages but **you don't want to migrate your existing data**:
```bash
mv /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds.$(date +%s)
@@ -353,7 +353,7 @@ Errno::EACCES: Permission denied @ rb_sysopen - config/secrets.yml
This can happen if you are updating from versions prior to 7.13 straight to 8.0.
The fix for this is to update to Omnibus 7.14 first and then update it to 8.0.
-### Permission denied when accessing /var/opt/gitlab/gitlab-ci/builds
+### Permission denied when accessing `/var/opt/gitlab/gitlab-ci/builds`
To fix that issue you have to change builds/ folder permission before doing final backup:
diff --git a/doc/push_rules/push_rules.md b/doc/push_rules/push_rules.md
index b1754131e76..7455b577af7 100644
--- a/doc/push_rules/push_rules.md
+++ b/doc/push_rules/push_rules.md
@@ -10,7 +10,7 @@ regular expressions to reject pushes based on commit contents, branch names or f
## Overview
GitLab already offers [protected branches][protected-branches], but there are
-cases when you need some specific rules like preventing git tag removal or
+cases when you need some specific rules like preventing Git tag removal or
enforcing a special format for commit messages.
Push rules are essentially [pre-receive Git hooks][hooks] that are easy to
@@ -27,7 +27,7 @@ Every push rule could have its own use case, but let's consider some examples.
Let's assume you have the following requirements for your workflow:
- every commit should reference a Jira issue, for example: `Refactored css. Fixes JIRA-123.`
-- users should not be able to remove git tags with `git push`
+- users should not be able to remove Git tags with `git push`
All you need to do is write a simple regular expression that requires the mention
of a Jira issue in the commit message, like `JIRA\-\d+`.
@@ -64,7 +64,7 @@ The following options are available.
| Push rule | GitLab version | Description |
| --------- | :------------: | ----------- |
-| Removal of tags with `git push` | **Starter** 7.10 | Forbid users to remove git tags with `git push`. Tags will still be able to be deleted through the web UI. |
+| Removal of tags with `git push` | **Starter** 7.10 | Forbid users to remove Git tags with `git push`. Tags will still be able to be deleted through the web UI. |
| Check whether author is a GitLab user | **Starter** 7.10 | Restrict commits by author (email) to existing GitLab users. |
| Committer restriction | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user |
| Check whether commit is signed through GPG | **Premium** 10.1 | Reject commit when it is not signed through GPG. Read [signing commits with GPG][signing-commits]. |
diff --git a/doc/security/README.md b/doc/security/README.md
index e3fb07c69c2..fe96f7f2846 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -18,3 +18,4 @@ type: index
- [Enforce Two-factor authentication](two_factor_authentication.md)
- [Send email confirmation on sign-up](user_email_confirmation.md)
- [Security of running jobs](https://docs.gitlab.com/runner/security/)
+- [Proxying images](asset_proxy.md)
diff --git a/doc/security/asset_proxy.md b/doc/security/asset_proxy.md
new file mode 100644
index 00000000000..f25910d3db7
--- /dev/null
+++ b/doc/security/asset_proxy.md
@@ -0,0 +1,28 @@
+A possible security concern when managing a public facing GitLab instance is
+the ability to steal a users IP address by referencing images in issues, comments, etc.
+
+For example, adding `![Example image](http://example.com/example.png)` to
+an issue description will cause the image to be loaded from the external
+server in order to be displayed. However this also allows the external server
+to log the IP address of the user.
+
+One way to mitigate this is by proxying any external images to a server you
+control. GitLab handles this by allowing you to run the "Camo" server
+[cactus/go-camo](https://github.com/cactus/go-camo#how-it-works).
+The image request is sent to the Camo server, which then makes the request for
+the original image. This way an attacker only ever seems the IP address
+of your Camo server.
+
+Once you have your Camo server up and running, you can configure GitLab to
+proxy image requests to it. The following settings are supported:
+
+| Attribute | Description |
+| ------------------------ | ----------- |
+| `asset_proxy_enabled` | (**If enabled, requires:** `asset_proxy_url`) Enable proxying of assets. |
+| `asset_proxy_secret_key` | Shared secret with the asset proxy server. |
+| `asset_proxy_url` | URL of the asset proxy server. |
+| `asset_proxy_whitelist` | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. |
+
+These can be set via the [Application setting API](../api/settings.md)
+
+Note that a GitLab restart is required to apply any changes.
diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md
index 749ccf924b5..7c3d7284f25 100644
--- a/doc/security/information_exclusivity.md
+++ b/doc/security/information_exclusivity.md
@@ -15,7 +15,7 @@ another project that is under their control, or onto another server.
Therefore, it is impossible to build access controls that prevent the
intentional sharing of source code by users that have access to the source code.
-This is an inherent feature of a DVCS. All git management systems have this
+This is an inherent feature of a DVCS. All Git management systems have this
limitation.
You can take steps to prevent unintentional sharing and information
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index b99bfb16829..09d29bf3446 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -77,9 +77,12 @@ authentication requests were received in a 3-minute period from a single IP addr
This applies only to Git requests and container registry (`/jwt/auth`) requests
(combined).
-This limit is reset by requests that authenticate successfully. For example, 29
-failed authentication requests followed by 1 successful request, followed by 29
-more failed authentication requests would not trigger a ban.
+This limit:
+
+- Is reset by requests that authenticate successfully. For example, 29
+ failed authentication requests followed by 1 successful request, followed by 29
+ more failed authentication requests would not trigger a ban.
+- Does not apply to JWT requests authenticated by `gitlab-ci-token`.
No response headers are provided.
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 6021e8cff1d..1432147ca5b 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -95,33 +95,45 @@ Auto DevOps.
To make full use of Auto DevOps, you will need:
-- **GitLab Runner** (needed for all stages) - Your Runner needs to be
- configured to be able to run Docker. Generally this means using the
- [Docker](https://docs.gitlab.com/runner/executors/docker.html) or [Kubernetes
- executor](https://docs.gitlab.com/runner/executors/kubernetes.html), with
+- **GitLab Runner** (for all stages)
+
+ Your Runner needs to be configured to be able to run Docker. Generally this
+ means using the either the [Docker](https://docs.gitlab.com/runner/executors/docker.html)
+ or [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html) executors, with
[privileged mode enabled](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode).
+
The Runners do not need to be installed in the Kubernetes cluster, but the
Kubernetes executor is easy to use and is automatically autoscaling.
Docker-based Runners can be configured to autoscale as well, using [Docker
- Machine](https://docs.gitlab.com/runner/install/autoscaling.html). Runners
- should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner)
+ Machine](https://docs.gitlab.com/runner/install/autoscaling.html).
+
+ Runners should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner)
for the entire GitLab instance, or [specific Runners](../../ci/runners/README.md#registering-a-specific-runner)
that are assigned to specific projects.
-- **Base domain** (needed for Auto Review Apps and Auto Deploy) - You will need
- a domain configured with wildcard DNS which is going to be used by all of your
- Auto DevOps applications. [Read the specifics](#auto-devops-base-domain).
-- **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) -
- To enable deployments, you will need Kubernetes 1.5+. You will need a [Kubernetes cluster][kubernetes-clusters]
- for the project.
- - **A load balancer** - You can use NGINX ingress by deploying it to your
- Kubernetes cluster using the
- [`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
- Helm chart.
-- **Prometheus** (needed for Auto Monitoring) - To enable Auto Monitoring, you
+- **Base domain** (for Auto Review Apps and Auto Deploy)
+
+ You will need a domain configured with wildcard DNS which is going to be used
+ by all of your Auto DevOps applications.
+
+ Read the [specifics](#auto-devops-base-domain).
+- **Kubernetes** (for Auto Review Apps, Auto Deploy, and Auto Monitoring)
+
+ To enable deployments, you will need:
+
+ - Kubernetes 1.5+.
+ - A [Kubernetes cluster][kubernetes-clusters] for the project.
+ - A load balancer. You can use NGINX ingress by deploying it to your
+ Kubernetes cluster by either:
+ - Using the [`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress) Helm chart.
+ - Installing the Ingress [GitLab Managed App](../../user/clusters/applications.md#ingress).
+- **Prometheus** (for Auto Monitoring)
+
+ To enable Auto Monitoring, you
will need Prometheus installed somewhere (inside or outside your cluster) and
configured to scrape your Kubernetes cluster. To get response metrics
(in addition to system metrics), you need to
[configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-nginx-ingress-monitoring).
+
The [Prometheus service](../../user/project/integrations/prometheus.md)
integration needs to be enabled for the project, or enabled as a
[default service template](../../user/project/integrations/services_templates.md)
@@ -414,7 +426,7 @@ report is created, it's uploaded as an artifact which you can later download and
check out.
Any licenses are also shown in the merge request widget. Read more how
-[License Compliance works](../../user/application_security/license_management/index.md).
+[License Compliance works](../../user/application_security/license_compliance/index.md).
### Auto Container Scanning **(ULTIMATE)**
@@ -694,6 +706,34 @@ will build a Docker image based on the Dockerfile rather than using buildpacks.
This can be much faster and result in smaller images, especially if your
Dockerfile is based on [Alpine](https://hub.docker.com/_/alpine/).
+### Passing arguments to `docker build`
+
+Arguments can be passed to the `docker build` command using the
+`AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` project variable.
+
+For example, to build a Docker image based on based on the `ruby:alpine`
+instead of the default `ruby:latest`:
+
+1. Set `AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` to `--build-arg=RUBY_VERSION=alpine`.
+1. Add the following to a custom `Dockerfile`:
+
+ ```docker
+ ARG RUBY_VERSION=latest
+ FROM ruby:$RUBY_VERSION
+
+ # ... put your stuff here
+ ```
+
+NOTE: **Note:**
+Passing in complex values (newlines and spaces, for example) will likely
+cause escaping issues due to the way this argument is used in Auto DevOps.
+Consider using Base64 encoding of such values to avoid this problem.
+
+CAUTION: **Warning:**
+Avoid passing secrets as Docker build arguments if possible, as they may be
+persisted in your image. See
+[this discussion](https://github.com/moby/moby/issues/13490) for details.
+
### Custom Helm Chart
Auto DevOps uses [Helm](https://helm.sh/) to deploy your application to Kubernetes.
@@ -782,6 +822,7 @@ applications.
|-----------------------------------------|------------------------------------|
| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
+| `AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` | Extra arguments to be passed to the `docker build` command. Note that using quotes will not prevent word splitting. [More details](#passing-arguments-to-docker-build). |
| `AUTO_DEVOPS_CHART` | Helm Chart used to deploy your apps. Defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). |
| `AUTO_DEVOPS_CHART_REPOSITORY` | Helm Chart repository used to search for charts. Defaults to `https://charts.gitlab.io`. |
| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, used to set the name of the helm repository. Defaults to `gitlab`. |
diff --git a/doc/topics/git/partial_clone.md b/doc/topics/git/partial_clone.md
index ea4223355d8..c9a5430b2c6 100644
--- a/doc/topics/git/partial_clone.md
+++ b/doc/topics/git/partial_clone.md
@@ -112,7 +112,7 @@ enabled on the Git server:
git init
# Add the remote
- git remote add origin git@gitlab.com/example/jumbo-repo
+ git remote add origin <url>
# Enable partial clone support for the remote
git config --local extensions.partialClone origin
diff --git a/doc/university/process/README.md b/doc/university/process/README.md
index b278e02ccd5..1a555f065ed 100644
--- a/doc/university/process/README.md
+++ b/doc/university/process/README.md
@@ -2,10 +2,6 @@
comments: false
---
----
-title: University | Process
----
-
# Suggesting improvements
If you would like to teach a class or participate or help in any way please
diff --git a/doc/university/training/gitlab_flow.md b/doc/university/training/gitlab_flow.md
index 0ce92be4994..66e645a0af8 100644
--- a/doc/university/training/gitlab_flow.md
+++ b/doc/university/training/gitlab_flow.md
@@ -23,8 +23,6 @@ type: reference
as opposed to individual stable branches
- Consider creating a tag for each version that gets deployed
-## Production branch
-
![inline](gitlab_flow/production_branch.png)
## Release branch
@@ -36,8 +34,6 @@ type: reference
- Cherry-pick critical bug fixes to stable branch for patch release
- Never commit bug fixes directly to stable branch
-## Release branch
-
![inline](gitlab_flow/release_branches.png)
## More details
diff --git a/doc/university/training/topics/git_intro.md b/doc/university/training/topics/git_intro.md
index 845bb7f0a81..c9a1cbb7839 100644
--- a/doc/university/training/topics/git_intro.md
+++ b/doc/university/training/topics/git_intro.md
@@ -15,7 +15,7 @@ comments: false
- Adapts to nearly any workflow
- Fast, reliable and stable file format
-## Help!
+## Help
Use the tools at your disposal when you get stuck.
diff --git a/doc/university/training/user_training.md b/doc/university/training/user_training.md
index e440652edfc..37107e13584 100644
--- a/doc/university/training/user_training.md
+++ b/doc/university/training/user_training.md
@@ -23,7 +23,7 @@ type: reference
- Adapts to nearly any workflow.
- Fast, reliable and stable file format.
-## Help!
+## Help
Use the tools at your disposal when you get stuck.
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 6a41c6aec32..3c9fcbf7c7d 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -64,7 +64,7 @@ sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production
sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096"
```
-### 4. Update gitlab-workhorse to the corresponding version
+### 4. Update GitLab Workhorse to the corresponding version
```bash
cd /home/git/gitlab
@@ -80,7 +80,7 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
```
-### 6. Update gitlab-shell to the corresponding version
+### 6. Update GitLab Shell to the corresponding version
```bash
cd /home/git/gitlab-shell
@@ -90,7 +90,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) -b v$(</h
sudo -u git -H sh -c 'if [ -x bin/compile ]; then bin/compile; fi'
```
-### 7. Update gitlab-pages to the corresponding version (skip if not using pages)
+### 7. Update GitLab Pages to the corresponding version (skip if not using pages)
```bash
cd /home/git/gitlab-pages
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index 49e2fb2568e..9ac73725f87 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -17,7 +17,7 @@ GitLab edition you are using (Community or Enterprise), see the
This guide assumes you have a correctly configured and tested installation of
GitLab Community Edition. If you run into any trouble or if you have any
-questions please contact us at [support@gitlab.com].
+questions please contact us at `support@gitlab.com`.
In all examples, replace `EE_BRANCH` with the Enterprise Edition branch for the
version you are using, and `CE_BRANCH` with the Community Edition branch.
@@ -131,5 +131,4 @@ Example:
Additional instructions here.
-->
-[support@gitlab.com]: mailto:support@gitlab.com
[old-ee-upgrade-docs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/11-8-stable-ee/doc/update
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index f6b64dcf694..890a5ec8698 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -118,7 +118,7 @@ sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.11.10.linux-amd64.tar.gz
```
-### 6. Update git
+### 6. Update Git
NOTE: **Note:**
GitLab 11.11 and higher only supports Git 2.21.x and newer, and
@@ -186,7 +186,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout BRANCH-ee
```
-### 8. Update gitlab-shell
+### 8. Update GitLab Shell
```bash
cd /home/git/gitlab-shell
@@ -196,9 +196,9 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
sudo -u git -H bin/compile
```
-### 9. Update gitlab-workhorse
+### 9. Update GitLab Workhorse
-Install and compile gitlab-workhorse. GitLab-Workhorse uses
+Install and compile GitLab Workhorse. GitLab Workhorse uses
[GNU Make](https://www.gnu.org/software/make/).
If you are not using Linux you may have to run `gmake` instead of
`make` below.
@@ -222,11 +222,11 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 11. Update gitlab-pages
+### 11. Update GitLab Pages
#### Only needed if you use GitLab Pages
-Install and compile gitlab-pages. GitLab-Pages uses
+Install and compile GitLab Pages. GitLab Pages uses
[GNU Make](https://www.gnu.org/software/make/).
If you are not using Linux you may have to run `gmake` instead of
`make` below.
@@ -273,8 +273,8 @@ longer handles setting it.
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
-will need to let gitlab-workhorse listen on a TCP port. You can do this
-via [/etc/default/gitlab].
+will need to let GitLab Workhorse listen on a TCP port. You can do this
+via [`/etc/default/gitlab`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example#L38).
#### SMTP configuration
@@ -404,4 +404,3 @@ If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of
[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/smtp_settings.rb.sample#L13
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
-[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example#L38
diff --git a/doc/update/upgrading_postgresql_using_slony.md b/doc/update/upgrading_postgresql_using_slony.md
index 835166837a8..cf3a389b149 100644
--- a/doc/update/upgrading_postgresql_using_slony.md
+++ b/doc/update/upgrading_postgresql_using_slony.md
@@ -77,7 +77,7 @@ make
make install
```
-This assumes you have installed GitLab into /opt/gitlab.
+This assumes you have installed GitLab into `/opt/gitlab`.
To test if Slony is installed properly, run the following commands:
diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md
index f5864e1f828..dbcf250bc57 100644
--- a/doc/user/admin_area/license.md
+++ b/doc/user/admin_area/license.md
@@ -117,4 +117,4 @@ questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index bd76b052422..fa14ecdf7cb 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -127,7 +127,7 @@ but commented out to help encourage others to add to it in the future. -->
GitLab administrators can force a pipeline configuration to run on every
pipeline.
-The configuration applies to all pipelines for a GitLab instance and is
+The configuration applies to all pipelines for a GitLab instance and is
sourced from:
- The [instance template repository](instance_template_repository.md).
diff --git a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
index 8e53a6995fb..6d2f74af660 100644
--- a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
+++ b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
@@ -6,7 +6,7 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30829) in GitLab 12.2.
-This setting allows you to rate limit the requests to raw endpoints, defaults to `300` requests per minute.
+This setting allows you to rate limit the requests to raw endpoints, defaults to `300` requests per minute.
It can be modified in **Admin Area > Network > Performance Optimization**.
For example, requests over `300` per minute to `https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/controllers/application_controller.rb` will be blocked. Access to the raw file will be released after 1 minute.
diff --git a/doc/user/admin_area/settings/terms.md b/doc/user/admin_area/settings/terms.md
index baf219ac9c7..0b5f9a13b03 100644
--- a/doc/user/admin_area/settings/terms.md
+++ b/doc/user/admin_area/settings/terms.md
@@ -4,8 +4,7 @@ type: reference
# Enforce accepting Terms of Service **(CORE ONLY)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18570)
-> in [GitLab Core](https://about.gitlab.com/pricing/) 10.8
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18570) in [GitLab Core](https://about.gitlab.com/pricing/) 10.8.
An admin can enforce acceptance of a terms of service and privacy policy. When this option is enabled, new and existing users must accept the terms.
diff --git a/doc/user/admin_area/settings/third_party_offers.md b/doc/user/admin_area/settings/third_party_offers.md
index ca26147b287..50dea8f50a2 100644
--- a/doc/user/admin_area/settings/third_party_offers.md
+++ b/doc/user/admin_area/settings/third_party_offers.md
@@ -4,8 +4,7 @@ type: reference
# Third party offers **(CORE ONLY)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20379)
-> in [GitLab Core](https://about.gitlab.com/pricing/) 11.1
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20379) in [GitLab Core](https://about.gitlab.com/pricing/) 11.1.
Within GitLab, we inform users of available third-party offers they might find valuable in order
to enhance the development of their projects. An example is the Google Cloud Platform free credit
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 7b631a5a1cd..a030f8d96ef 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -96,7 +96,7 @@ them in a YAML file named `clair-whitelist.yml`. Read more in the
## Example
-The following is a sample `.gitlab-ci.yml` that will build your Docker Image, push it to the container registry and run Container Scanning.
+The following is a sample `.gitlab-ci.yml` that will build your Docker Image, push it to the container registry and run Container Scanning.
```yaml
variables:
@@ -155,4 +155,4 @@ docker: Error response from daemon: failed to copy xattrs: failed to set xattr "
This is a result of a bug in Docker which is now [fixed](https://github.com/containerd/continuity/pull/138 "fs: add WithAllowXAttrErrors CopyOpt").
To prevent the error, ensure the Docker version that the Runner is using is
`18.09.03` or higher. For more information, see
-[issue #10241](https://gitlab.com/gitlab-org/gitlab-ee/issues/10241 "Investigate why Container Scanning is not working with NFS mounts"). \ No newline at end of file
+[issue #10241](https://gitlab.com/gitlab-org/gitlab-ee/issues/10241 "Investigate why Container Scanning is not working with NFS mounts").
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index fcd683ca2db..5a1cc0561fc 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -153,7 +153,7 @@ Clicking on this button will create a merge request to apply the solution onto t
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9928) in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.2.
-Merge Request Approvals can be configured to require approval from a member
+Merge Request Approvals can be configured to require approval from a member
of your security team when a vulnerability would be introduced by a merge request.
This threshold is defined as `high`, `critical`, or `unknown`
diff --git a/doc/user/application_security/license_management/img/license_management.png b/doc/user/application_security/license_compliance/img/license_compliance.png
index cdce6b5fe38..cdce6b5fe38 100644
--- a/doc/user/application_security/license_management/img/license_management.png
+++ b/doc/user/application_security/license_compliance/img/license_compliance.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_add_license.png b/doc/user/application_security/license_compliance/img/license_compliance_add_license.png
index c9a5dc14c57..c9a5dc14c57 100644
--- a/doc/user/application_security/license_management/img/license_management_add_license.png
+++ b/doc/user/application_security/license_compliance/img/license_compliance_add_license.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_decision.png b/doc/user/application_security/license_compliance/img/license_compliance_decision.png
index fbf90bec7fd..fbf90bec7fd 100644
--- a/doc/user/application_security/license_management/img/license_management_decision.png
+++ b/doc/user/application_security/license_compliance/img/license_compliance_decision.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_pipeline_tab.png b/doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab.png
index 80ffca815b9..80ffca815b9 100644
--- a/doc/user/application_security/license_management/img/license_management_pipeline_tab.png
+++ b/doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_search.png b/doc/user/application_security/license_compliance/img/license_compliance_search.png
index b3ffd8d95a1..b3ffd8d95a1 100644
--- a/doc/user/application_security/license_management/img/license_management_search.png
+++ b/doc/user/application_security/license_compliance/img/license_compliance_search.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_settings.png b/doc/user/application_security/license_compliance/img/license_compliance_settings.png
index 2e3e8888e93..2e3e8888e93 100644
--- a/doc/user/application_security/license_management/img/license_management_settings.png
+++ b/doc/user/application_security/license_compliance/img/license_compliance_settings.png
Binary files differ
diff --git a/doc/user/application_security/license_compliance/index.md b/doc/user/application_security/license_compliance/index.md
new file mode 100644
index 00000000000..f74b958cf67
--- /dev/null
+++ b/doc/user/application_security/license_compliance/index.md
@@ -0,0 +1,243 @@
+---
+type: reference, howto
+---
+
+# License Compliance **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5483) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
+
+## Overview
+
+If you are using [GitLab CI/CD](../../../ci/README.md), you can search your project dependencies for their licenses
+using License Compliance.
+
+You can take advantage of License Compliance by either [including the job](#configuration)
+in your existing `.gitlab-ci.yml` file or by implicitly using
+[Auto License Compliance](../../../topics/autodevops/index.md#auto-license-compliance-ultimate)
+that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
+
+GitLab checks the License Compliance report, compares the licenses between the
+source and target branches, and shows the information right on the merge request.
+Blacklisted licenses will be clearly visible with an `x` red icon next to them
+as well as new licenses which need a decision from you. In addition, you can
+[manually approve or blacklist](#project-policies-for-license-compliance)
+licenses in your project's settings.
+
+NOTE: **Note:**
+If the license compliance report doesn't have anything to compare to, no information
+will be displayed in the merge request area. That is the case when you add the
+`license_management` job in your `.gitlab-ci.yml` for the first time.
+Consecutive merge requests will have something to compare to and the license
+compliance report will be shown properly.
+
+![License Compliance Widget](img/license_compliance.png)
+
+If you are a project or group Maintainer, you can click on a license to be given
+the choice to approve it or blacklist it.
+
+![License approval decision](img/license_compliance_decision.png)
+
+## Use cases
+
+It helps you find what licenses your project uses in its dependencies, and decide for each of then
+whether to allow it or forbid it. For example, your application is using an external (open source)
+library whose license is incompatible with yours.
+
+## Supported languages and package managers
+
+The following languages and package managers are supported.
+
+| Language | Package managers | Scan Tool |
+|------------|-------------------------------------------------------------------|----------------------------------------------------------|
+| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Go | [Godep](https://github.com/tools/godep), go get ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), gvt ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), glide ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), dep ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), trash ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) and govendor ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), [go mod](https://github.com/golang/go/wiki/Modules) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| .NET | [Nuget](https://www.nuget.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Python | [pip](https://pip.pypa.io/en/stable/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Ruby | [gem](https://rubygems.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Erlang | [rebar](https://www.rebar3.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) , [CocoaPods v0.39 and below](https://cocoapods.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Elixir | [mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| C++/C | [conan](https://conan.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Scala | [sbt](https://www.scala-sbt.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Rust | [cargo](https://crates.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+| PHP | [composer](https://getcomposer.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+
+## Requirements
+
+To run a License Compliance scanning job, you need GitLab Runner with the
+[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
+
+## Configuration
+
+For GitLab 11.9 and later, to enable License Compliance, you must
+[include](../../../ci/yaml/README.md#includetemplate) the
+[`License-Management.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml)
+that's provided as a part of your GitLab installation.
+For GitLab versions earlier than 11.9, you can copy and use the job as defined
+that template.
+
+Add the following to your `.gitlab-ci.yml` file:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+```
+
+The included template will create a `license_management` job in your CI/CD pipeline
+and scan your dependencies to find their licenses.
+
+The results will be saved as a
+[License Compliance report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate)
+that you can later download and analyze. Due to implementation limitations, we
+always take the latest License Compliance artifact available. Behind the scenes, the
+[GitLab License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
+is used to detect the languages/frameworks and in turn analyzes the licenses.
+
+The License Compliance settings can be changed through environment variables by using the
+[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [License Compliance documentation](https://gitlab.com/gitlab-org/security-products/license-management#settings).
+
+### Installing custom dependencies
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
+
+The `license_management` image already embeds many auto-detection scripts, languages,
+and packages. Nevertheless, it's almost impossible to cover all cases for all projects.
+That's why sometimes it's necessary to install extra packages, or to have extra steps
+in the project automated setup, like the download and installation of a certificate.
+For that, a `LICENSE_MANAGEMENT_SETUP_CMD` environment variable can be passed to the container,
+with the required commands to run before the license detection.
+
+If present, this variable will override the setup step necessary to install all the packages
+of your application (e.g.: for a project with a `Gemfile`, the setup step could be
+`bundle install`).
+
+For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+variables:
+ LICENSE_MANAGEMENT_SETUP_CMD: sh my-custom-install-script.sh
+```
+
+In this example, `my-custom-install-script.sh` is a shell script at the root
+directory of your project.
+
+### Overriding the template
+
+If you want to override the job definition (for example, change properties like
+`variables` or `dependencies`), you need to declare a `license_management` job
+after the template inclusion and specify any additional keys under it. For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+license_management:
+ variables:
+ CI_DEBUG_TRACE: "true"
+```
+
+### Configuring Maven projects
+
+The License Compliance tool provides a `MAVEN_CLI_OPTS` environment variable which can hold
+the command line arguments to pass to the `mvn install` command which is executed under the hood.
+Feel free to use it for the customization of Maven execution. For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+license_management:
+ variables:
+ MAVEN_CLI_OPTS: --debug
+```
+
+`mvn install` runs through all of the [build life cycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
+stages prior to `install`, including `test`. Running unit tests is not directly
+necessary for the license scanning purposes and consumes time, so it's skipped
+by having the default value of `MAVEN_CLI_OPTS` as `-DskipTests`. If you want
+to supply custom `MAVEN_CLI_OPTS` and skip tests at the same time, don't forget
+to explicitly add `-DskipTests` to your options.
+If you still need to run tests during `mvn install`, add `-DskipTests=false` to
+`MAVEN_CLI_OPTS`.
+
+### Selecting the version of Python
+
+> - [Introduced](https://gitlab.com/gitlab-org/security-products/license-management/merge_requests/36) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+> - In GitLab 12.2, Python 3.5 became the default.
+
+License Compliance uses Python 3.5 and pip 19.1 by default.
+If your project requires Python 2, you can switch to Python 2.7 and pip 10.0
+by setting the `LM_PYTHON_VERSION` environment variable to `2`.
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+license_management:
+ variables:
+ LM_PYTHON_VERSION: 2
+```
+
+## Project policies for License Compliance
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5940) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
+
+From the project's settings:
+
+- The list of licenses and their status can be managed.
+- Licenses can be manually approved or blacklisted.
+
+To approve or blacklist a license:
+
+1. Either use the **Manage licenses** button in the merge request widget, or
+ navigate to the project's **Settings > CI/CD** and expand the
+ **License Compliance** section.
+1. Click the **Add a license** button.
+
+ ![License Compliance Add License](img/license_compliance_add_license.png)
+
+1. In the **License name** dropdown, either:
+ - Select one of the available licenses. You can search for licenses in the field
+ at the top of the list.
+ - Enter arbitrary text in the field at the top of the list. This will cause the text to be
+ added as a license name to the list.
+1. Select the **Approve** or **Blacklist** radio button to approve or blacklist respectively
+ the selected license.
+
+To modify an existing license:
+
+1. In the **License Compliance** list, click the **Approved/Declined** dropdown to change it to the desired status.
+
+ ![License Compliance Settings](img/license_compliance_settings.png)
+
+Searching for Licenses:
+
+1. Use the **Search** box to search for a specific license.
+
+ ![License Compliance Search](img/license_compliance_search.png)
+
+## License Compliance report under pipelines
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5491) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2.
+
+From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on the
+pipeline ID that has a `license_management` job to see the Licenses tab with the listed
+licenses (if any).
+
+![License Compliance Pipeline Tab](img/license_compliance_pipeline_tab.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/license_management/index.md b/doc/user/application_security/license_management/index.md
index 44b2671930e..319da2c3a6e 100644
--- a/doc/user/application_security/license_management/index.md
+++ b/doc/user/application_security/license_management/index.md
@@ -1,245 +1,5 @@
---
-type: reference, howto
+redirect_to: ../license_compliance/index.md
---
-# License Compliance **(ULTIMATE)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5483)
-in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
-
-## Overview
-
-If you are using [GitLab CI/CD](../../../ci/README.md), you can search your project dependencies for their licenses
-using License Compliance.
-
-You can take advantage of License Compliance by either [including the job](#configuration)
-in your existing `.gitlab-ci.yml` file or by implicitly using
-[Auto License Compliance](../../../topics/autodevops/index.md#auto-license-compliance-ultimate)
-that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
-
-GitLab checks the License Compliance report, compares the licenses between the
-source and target branches, and shows the information right on the merge request.
-Blacklisted licenses will be clearly visible with an `x` red icon next to them
-as well as new licenses which need a decision from you. In addition, you can
-[manually approve or blacklist](#project-policies-for-license-compliance)
-licenses in your project's settings.
-
-NOTE: **Note:**
-If the license management report doesn't have anything to compare to, no information
-will be displayed in the merge request area. That is the case when you add the
-`license_management` job in your `.gitlab-ci.yml` for the first time.
-Consecutive merge requests will have something to compare to and the license
-management report will be shown properly.
-
-![License Compliance Widget](img/license_management.png)
-
-If you are a project or group Maintainer, you can click on a license to be given
-the choice to approve it or blacklist it.
-
-![License approval decision](img/license_management_decision.png)
-
-## Use cases
-
-It helps you find what licenses your project uses in its dependencies, and decide for each of then
-whether to allow it or forbid it. For example, your application is using an external (open source)
-library whose license is incompatible with yours.
-
-## Supported languages and package managers
-
-The following languages and package managers are supported.
-
-| Language | Package managers | Scan Tool |
-|------------|-------------------------------------------------------------------|----------------------------------------------------------|
-| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Go | [Godep](https://github.com/tools/godep), go get ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), gvt ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), glide ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), dep ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), trash ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) and govendor ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), [go mod](https://github.com/golang/go/wiki/Modules) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| .NET | [Nuget](https://www.nuget.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Python | [pip](https://pip.pypa.io/en/stable/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Ruby | [gem](https://rubygems.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Erlang | [rebar](https://www.rebar3.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) , [CocoaPods v0.39 and below](https://cocoapods.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Elixir | [mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| C++/C | [conan](https://conan.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Scala | [sbt](https://www.scala-sbt.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Rust | [cargo](https://crates.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-| PHP | [composer](https://getcomposer.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-
-## Requirements
-
-To run a License Compliance scanning job, you need GitLab Runner with the
-[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
-
-## Configuration
-
-For GitLab 11.9 and later, to enable License Compliance, you must
-[include](../../../ci/yaml/README.md#includetemplate) the
-[`License-Management.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml)
-that's provided as a part of your GitLab installation.
-For GitLab versions earlier than 11.9, you can copy and use the job as defined
-that template.
-
-Add the following to your `.gitlab-ci.yml` file:
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-```
-
-The included template will create a `license_management` job in your CI/CD pipeline
-and scan your dependencies to find their licenses.
-
-The results will be saved as a
-[License Compliance report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate)
-that you can later download and analyze. Due to implementation limitations, we
-always take the latest License Compliance artifact available. Behind the scenes, the
-[GitLab License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
-is used to detect the languages/frameworks and in turn analyzes the licenses.
-
-The License Compliance settings can be changed through environment variables by using the
-[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [License Compliance documentation](https://gitlab.com/gitlab-org/security-products/license-management#settings).
-
-### Installing custom dependencies
-
-> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
-
-The `license_management` image already embeds many auto-detection scripts, languages,
-and packages. Nevertheless, it's almost impossible to cover all cases for all projects.
-That's why sometimes it's necessary to install extra packages, or to have extra steps
-in the project automated setup, like the download and installation of a certificate.
-For that, a `LICENSE_MANAGEMENT_SETUP_CMD` environment variable can be passed to the container,
-with the required commands to run before the license detection.
-
-If present, this variable will override the setup step necessary to install all the packages
-of your application (e.g.: for a project with a `Gemfile`, the setup step could be
-`bundle install`).
-
-For example:
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-
-variables:
- LICENSE_MANAGEMENT_SETUP_CMD: sh my-custom-install-script.sh
-```
-
-In this example, `my-custom-install-script.sh` is a shell script at the root
-directory of your project.
-
-### Overriding the template
-
-If you want to override the job definition (for example, change properties like
-`variables` or `dependencies`), you need to declare a `license_management` job
-after the template inclusion and specify any additional keys under it. For example:
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-
-license_management:
- variables:
- CI_DEBUG_TRACE: "true"
-```
-
-### Configuring Maven projects
-
-The License Compliance tool provides a `MAVEN_CLI_OPTS` environment variable which can hold
-the command line arguments to pass to the `mvn install` command which is executed under the hood.
-Feel free to use it for the customization of Maven execution. For example:
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-
-license_management:
- variables:
- MAVEN_CLI_OPTS: --debug
-```
-
-`mvn install` runs through all of the [build life cycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
-stages prior to `install`, including `test`. Running unit tests is not directly
-necessary for the license scanning purposes and consumes time, so it's skipped
-by having the default value of `MAVEN_CLI_OPTS` as `-DskipTests`. If you want
-to supply custom `MAVEN_CLI_OPTS` and skip tests at the same time, don't forget
-to explicitly add `-DskipTests` to your options.
-If you still need to run tests during `mvn install`, add `-DskipTests=false` to
-`MAVEN_CLI_OPTS`.
-
-### Selecting the version of Python
-
-> [Introduced](https://gitlab.com/gitlab-org/security-products/license-management/merge_requests/36) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
-
-License Compliance uses Python 3.5 and pip 19.1 by default.
-If your project requires Python 2, you can switch to Python 2.7 and pip 10.0
-by setting the `LM_PYTHON_VERSION` environment variable to `2`.
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-
-license_management:
- variables:
- LM_PYTHON_VERSION: 2
-```
-
-## Project policies for License Compliance
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5940)
-in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
-
-From the project's settings:
-
-- The list of licenses and their status can be managed.
-- Licenses can be manually approved or blacklisted.
-
-To approve or blacklist a license:
-
-1. Either use the **Manage licenses** button in the merge request widget, or
- navigate to the project's **Settings > CI/CD** and expand the
- **License Compliance** section.
-1. Click the **Add a license** button.
-
- ![License Compliance Add License](img/license_management_add_license.png)
-
-1. In the **License name** dropdown, either:
- - Select one of the available licenses. You can search for licenses in the field
- at the top of the list.
- - Enter arbitrary text in the field at the top of the list. This will cause the text to be
- added as a license name to the list.
-1. Select the **Approve** or **Blacklist** radio button to approve or blacklist respectively
- the selected license.
-
-To modify an existing license:
-
-1. In the **License Compliance** list, click the **Approved/Declined** dropdown to change it to the desired status.
-
- ![License Compliance Settings](img/license_management_settings.png)
-
-Searching for Licenses:
-
-1. Use the **Search** box to search for a specific license.
-
- ![License Compliance Search](img/license_management_search.png)
-
-## License Compliance report under pipelines
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5491)
-in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2.
-
-From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on the
-pipeline ID that has a `license_management` job to see the Licenses tab with the listed
-licenses (if any).
-
-![License Compliance Pipeline Tab](img/license_management_pipeline_tab.png)
-
-<!-- ## Troubleshooting
-
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
-
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+This document was moved to [another location](../license_compliance/index.md).
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index ac8c1ac0354..314f7c1766f 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -62,6 +62,9 @@ Once you're on the dashboard, at the top you should see a series of filters for:
- Report type
- Project
+NOTE: **Note:**
+The dashboard only shows projects with [security reports](#supported-reports) enabled in a group.
+
![dashboard with action buttons and metrics](img/group_security_dashboard.png)
Selecting one or more filters will filter the results in this page.
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index 096730f800c..40ed0db4c57 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -103,7 +103,7 @@ implications](../project/clusters/index.md#security-implications) before doing s
NOTE: **Note:**
The
-[runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner)
+[runner/gitlab-runner](https://gitlab.com/gitlab-org/charts/gitlab-runner)
chart is used to install this application with a
[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
file.
@@ -225,8 +225,7 @@ file.
## Upgrading applications
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789)
-in GitLab 11.8.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789) in GitLab 11.8.
The applications below can be upgraded.
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 6891682141c..6b748981106 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -452,7 +452,7 @@ Replying to a non-thread comment will convert the non-thread comment to a
thread once the reply is submitted. This conversion is considered an edit
to the original comment, so a note about when it was last edited will appear underneath it.
-This feature only exists for Issues, Merge requests, and Epics. Commits, Snippets and Merge request diff threads are
+This feature only exists for Issues, Merge requests, and Epics. Commits, Snippets and Merge request diff threads are
not supported yet.
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index af37cc896ad..548e029f81f 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -314,15 +314,18 @@ Source:
#### Git and container registry failed authentication ban
-GitLab.com responds with HTTP status code 403 for 1 hour, if 30 failed
+GitLab.com responds with HTTP status code `403` for 1 hour, if 30 failed
authentication requests were received in a 3-minute period from a single IP address.
This applies only to Git requests and container registry (`/jwt/auth`) requests
(combined).
-This limit is reset by requests that authenticate successfully. For example, 29
-failed authentication requests followed by 1 successful request, followed by 29
-more failed authentication requests would not trigger a ban.
+This limit:
+
+- Is reset by requests that authenticate successfully. For example, 29
+ failed authentication requests followed by 1 successful request, followed by 29
+ more failed authentication requests would not trigger a ban.
+- Does not apply to JWT requests authenticated by `gitlab-ci-token`.
No response headers are provided.
@@ -334,7 +337,7 @@ GitLab.com does not currently use these settings.
In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses
the following applications and settings to achieve scale. All settings are
-located publicly available [chef cookbooks](https://gitlab.com/gitlab-cookbooks).
+publicly available at [chef cookbooks](https://gitlab.com/gitlab-cookbooks).
### ELK
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 864f1a397d5..403071f2513 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -358,7 +358,7 @@ the group regardless of the IP restriction.
You can restrict access to groups and their underlying projects by
allowing only users with email addresses in particular domains to be added to the group.
-Add email domains you want to whitelist and users with emails from different
+Add email domains you want to whitelist and users with emails from different
domains won't be allowed to be added to this group.
Some domains cannot be restricted. These are the most popular public email domains, such as:
@@ -417,7 +417,7 @@ You can disable all email notifications related to the group, which also include
it's subgroups and projects.
To enable this feature:
-
+
1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and select **Disable email notifications**.
1. Click **Save changes**.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 17bbed2945d..edf2fedab3c 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -181,9 +181,6 @@ graph TD;
#### Subgraphs
-NOTE: **Note:** GitLab 12.1 and up now [requires quotes around subgraph
-titles that contain multiple words](https://github.com/knsv/mermaid/pull/845).
-
Subgraphs can also be included:
~~~
@@ -1151,7 +1148,7 @@ GFM will autolink almost any URL you put into your text:
- https://google.com/
- ftp://ftp.us.debian.org/debian/
- smb://foo/bar/baz
-- irc://irc.freenode.net/gitlab
+- irc://irc.freenode.net/
- http://localhost:3000
```
@@ -1159,7 +1156,7 @@ GFM will autolink almost any URL you put into your text:
- <https://google.com/>
- <ftp://ftp.us.debian.org/debian/>
- <smb://foo/bar/baz>
-- <irc://irc.freenode.net/gitlab>
+- <irc://irc.freenode.net/>
- <http://localhost:3000>
### Lists
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 80d1bf992ec..c28a5e49ec4 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -204,27 +204,29 @@ Any user can remove themselves from a group, unless they are the last Owner of
the group. The following table depicts the various user permission levels in a
group.
-| Action | Guest | Reporter | Developer | Maintainer | Owner |
-|-------------------------------------------------|-------|----------|-----------|------------|-------|
-| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View Insights charts **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View group epic **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Create/edit group epic **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
-| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
-| Create project in group | | | ✓ | ✓ | ✓ |
-| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
-| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
-| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
-| Create subgroup | | | | ✓ (1) | ✓ |
-| Edit group | | | | | ✓ |
-| Manage group members | | | | | ✓ |
-| Remove group | | | | | ✓ |
-| Delete group epic **(ULTIMATE)** | | | | | ✓ |
-| View group Audit Events | | | | | ✓ |
-| Disable notification emails | | | | | ✓ |
+| Action | Guest | Reporter | Developer | Maintainer | Owner |
+|--------------------------------------------------------|-------|----------|-----------|------------|-------|
+| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View Insights charts **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View group epic **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Create/edit group epic **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
+| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
+| Create project in group | | | ✓ | ✓ | ✓ |
+| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
+| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
+| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
+| Create subgroup | | | | ✓ (1) | ✓ |
+| Edit group | | | | | ✓ |
+| Manage group members | | | | | ✓ |
+| Remove group | | | | | ✓ |
+| Delete group epic **(ULTIMATE)** | | | | | ✓ |
+| Edit epic comments (posted by any user) **(ULTIMATE)** | | | | ✓ (2) | ✓ (2) |
+| View group Audit Events | | | | | ✓ |
+| Disable notification emails | | | | | ✓ |
- (1): Groups can be set to [allow either Owners or Owners and
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
+- (2): Introduced in GitLab 12.2.
### Subgroup permissions
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index cf3a3fef79f..3bde0a375c6 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -199,7 +199,7 @@ To add an existing Kubernetes cluster to your project:
kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'
```
- - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the EKS cluster. We will use the certificate created by default.
+ - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the cluster. We will use the certificate created by default.
- List the secrets with `kubectl get secrets`, and one should named similar to
`default-token-xxxxx`. Copy that token name for use below.
- Get the certificate by running this command:
diff --git a/doc/user/project/code_owners.md b/doc/user/project/code_owners.md
index 96c4f16fe04..79f0bb4db72 100644
--- a/doc/user/project/code_owners.md
+++ b/doc/user/project/code_owners.md
@@ -58,7 +58,7 @@ Example `CODEOWNERS` file:
\#file_with_pound.rb @owner-file-with-pound
# Multiple codeowners can be specified, separated by spaces or tabs
-CODEOWNERS @multiple @code @owners
+CODEOWNERS @multiple @code @owners
# Both usernames or email addresses can be used to match
# users. Everything else will be ignored. For example this will
diff --git a/doc/user/project/img/protected_branches_devs_can_push.png b/doc/user/project/img/protected_branches_devs_can_push.png
deleted file mode 100644
index b537839c00b..00000000000
--- a/doc/user/project/img/protected_branches_devs_can_push.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_devs_can_push_v12_3.png b/doc/user/project/img/protected_branches_devs_can_push_v12_3.png
new file mode 100644
index 00000000000..adc03a41abb
--- /dev/null
+++ b/doc/user/project/img/protected_branches_devs_can_push_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_branches_list.png b/doc/user/project/img/protected_branches_list.png
deleted file mode 100644
index 495ce4d7b6f..00000000000
--- a/doc/user/project/img/protected_branches_list.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_list_v12_3.png b/doc/user/project/img/protected_branches_list_v12_3.png
new file mode 100644
index 00000000000..365d8d99e5a
--- /dev/null
+++ b/doc/user/project/img/protected_branches_list_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_branches_page.png b/doc/user/project/img/protected_branches_page.png
deleted file mode 100644
index 9b10991f62e..00000000000
--- a/doc/user/project/img/protected_branches_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_page_v12_3.png b/doc/user/project/img/protected_branches_page_v12_3.png
new file mode 100644
index 00000000000..17f19642552
--- /dev/null
+++ b/doc/user/project/img/protected_branches_page_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_tags_list.png b/doc/user/project/img/protected_tags_list.png
deleted file mode 100644
index 6c5295e0f4b..00000000000
--- a/doc/user/project/img/protected_tags_list.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_tags_list_v12_3.png b/doc/user/project/img/protected_tags_list_v12_3.png
new file mode 100644
index 00000000000..6a30f615f2f
--- /dev/null
+++ b/doc/user/project/img/protected_tags_list_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_tags_page.png b/doc/user/project/img/protected_tags_page.png
deleted file mode 100644
index 5f8a2106cd1..00000000000
--- a/doc/user/project/img/protected_tags_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_tags_page_v12_3.png b/doc/user/project/img/protected_tags_page_v12_3.png
new file mode 100644
index 00000000000..841e19af8a7
--- /dev/null
+++ b/doc/user/project/img/protected_tags_page_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_tags_permissions_dropdown.png b/doc/user/project/img/protected_tags_permissions_dropdown.png
deleted file mode 100644
index 77098eeb591..00000000000
--- a/doc/user/project/img/protected_tags_permissions_dropdown.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_tags_permissions_dropdown_v12_3.png b/doc/user/project/img/protected_tags_permissions_dropdown_v12_3.png
new file mode 100644
index 00000000000..913d4725d53
--- /dev/null
+++ b/doc/user/project/img/protected_tags_permissions_dropdown_v12_3.png
Binary files differ
diff --git a/doc/user/project/import/tfvc.md b/doc/user/project/import/tfvc.md
index 375522b77d0..9b148224e10 100644
--- a/doc/user/project/import/tfvc.md
+++ b/doc/user/project/import/tfvc.md
@@ -6,7 +6,7 @@ type: concepts
Team Foundation Server (TFS), renamed [Azure DevOps Server](https://azure.microsoft.com/en-us/services/devops/server/)
in 2019, is a set of tools developed by Microsoft which also includes
-[Team Foundation Version Control](https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/overview)
+[Team Foundation Version Control](https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/overview?view=azure-devops)
(TFVC), a centralized version control system similar to Git.
In this document, we focus on the TFVC to Git migration.
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 64c4066683b..c63d5308536 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -98,7 +98,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Maven packages](packages/maven_repository.md): your private Maven repository in GitLab. **(PREMIUM)**
- [NPM packages](packages/npm_registry.md): your private NPM package registry in GitLab. **(PREMIUM)**
- [Code owners](code_owners.md): specify code owners for certain files **(STARTER)**
-- [License Compliance](../application_security/license_management/index.md): approve and blacklist licenses for projects. **(ULTIMATE)**
+- [License Compliance](../application_security/license_compliance/index.md): approve and blacklist licenses for projects. **(ULTIMATE)**
- [Dependency List](../application_security/dependency_list/index.md): view project dependencies. **(ULTIMATE)**
### Project integrations
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index 3206a39dc08..94e0c9fd886 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -57,5 +57,5 @@ service in GitLab.
If builds are not triggered, ensure you entered the right GitLab IP address in
Bamboo under 'Trigger IP addresses'.
-> **Note:**
-> - Starting with GitLab 8.14.0, builds are triggered on push events.
+NOTE: **Note:**
+Starting with GitLab 8.14.0, builds are triggered on push events.
diff --git a/doc/user/project/integrations/mattermost.md b/doc/user/project/integrations/mattermost.md
index 6e0f39956d3..c240b6cb6b4 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -13,8 +13,13 @@ To enable Mattermost integration you must create an incoming webhook integration
1. Choose a display name, description and channel, those can be overridden on GitLab.
1. Save it, copy the **Webhook URL**, we'll need this later for GitLab.
-There might be some cases that Incoming Webhooks are blocked by admin, ask your mattermost admin to enable
-it on **Mattermost System Console > Integrations > Integration Management**, or on **Mattermost System Console > Integrations > Custom Integrations** in Mattermost versions 5.11 and earlier.
+Incoming Webhooks might be blocked on your Mattermost instance. Ask your Mattermost admin
+to enable it on:
+
+- **Mattermost System Console > Integrations > Integration Management** in Mattermost
+ versions 5.12 and later.
+- **Mattermost System Console > Integrations > Custom Integrations** in Mattermost
+ versions 5.11 and earlier.
Display name override is not enabled by default, you need to ask your admin to enable it on that same section.
diff --git a/doc/user/project/integrations/mock_ci.md b/doc/user/project/integrations/mock_ci.md
index 886094a6531..b06ccda8287 100644
--- a/doc/user/project/integrations/mock_ci.md
+++ b/doc/user/project/integrations/mock_ci.md
@@ -6,7 +6,7 @@ To set up the mock CI service server, respond to the following endpoints
- `commit_status`: `#{project.namespace.path}/#{project.path}/status/#{sha}.json`
- Have your service return `200 { status: ['failed'|'canceled'|'running'|'pending'|'success'|'success-with-warnings'|'skipped'|'not_found'] }`
- - If the service returns a 404, it is interpreted as `pending`
+ - If the service returns a 404, it is interpreted as `pending`
- `build_page`: `#{project.namespace.path}/#{project.path}/status/#{sha}`
- Just where the build is linked to, doesn't matter if implemented
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index a80332adc46..d13592559b9 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -19,7 +19,7 @@ Once enabled, GitLab will automatically detect metrics from known services in th
### Managed Prometheus on Kubernetes
-> **Note**: [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28916) in GitLab 10.5
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28916) in GitLab 10.5.
GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cluster](../clusters/index.md), making monitoring of your apps easy.
@@ -271,7 +271,7 @@ Note the following properties:
### Downloading data as CSV
-Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
+Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
![Downloading as CSV](img/download_as_csv.png)
@@ -342,15 +342,21 @@ If the metric exceeds the threshold of the alert for over 5 minutes, an email wi
## Determining the performance impact of a merge
-> [Introduced][ce-10408] in GitLab 9.2.
-> GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
-> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics
+> - [Introduced][ce-10408] in GitLab 9.2.
+> - GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
Developers can view the performance impact of their changes within the merge
-request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot
-indicates when the current changes were deployed, with up to 30 minutes of
-performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after
-each commit has been deployed.
+request workflow.
+
+NOTE: **Note:**
+Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
+
+When a source branch has been deployed to an environment, a sparkline and
+numeric comparison of the average memory consumption will appear. On the
+sparkline, a dot indicates when the current changes were deployed, with up to 30 minutes of
+performance data displayed before and after. The comparison shows the difference
+between the 30 minute average before and after the deployment. This information
+is updated after each commit has been deployed.
Once merged and the target branch has been redeployed, the metrics will switch
to show the new environments this revision has been deployed to.
@@ -363,13 +369,15 @@ Prometheus server.
## Embedding metric charts within GitLab Flavored Markdown
> [Introduced][ce-29691] in GitLab 12.2.
-> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
It is possible to display metrics charts within [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
+NOTE: **Note:**
+Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
+
To display a metric chart, include a link of the form `https://<root_url>/<project>/environments/<environment_id>/metrics`.
-A single chart may also be embedded. You can generate a link to the chart via the dropdown located on the right side of the chart:
+A single chart may also be embedded. You can generate a link to the chart via the dropdown located on the right side of the chart:
![Generate Link To Chart](img/generate_link_to_chart.png)
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 84adb9637fc..f5bc6cbd988 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -2,6 +2,7 @@
> **Note:**
> Starting from GitLab 8.5:
+>
> - the `repository` key is deprecated in favor of the `project` key
> - the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key
> - the `project.http_url` key is deprecated in favor of the `project.git_http_url` key
@@ -12,6 +13,7 @@
>
> **Note:**
> Starting from GitLab 11.2:
+>
> - The `description` field for issues, merge requests, comments, and wiki pages
> is rewritten so that simple Markdown image references (like
> `![](/uploads/...)`) have their target URL changed to an absolute URL. See
@@ -98,11 +100,12 @@ Below are described the supported events.
Triggered when you push to the repository except when pushing tags.
-> **Note:** When more than 20 commits are pushed at once, the `commits` webhook
- attribute will only contain the first 20 for performance reasons. Loading
- detailed commit data is expensive. Note that despite only 20 commits being
- present in the `commits` attribute, the `total_commits_count` attribute will
- contain the actual total.
+NOTE: **Note:**
+When more than 20 commits are pushed at once, the `commits` webhook
+attribute will only contain the first 20 for performance reasons. Loading
+detailed commit data is expensive. Note that despite only 20 commits being
+present in the `commits` attribute, the `total_commits_count` attribute will
+contain the actual total.
**Request header**:
@@ -1181,20 +1184,20 @@ X-Gitlab-Event: Job Hook
```json
{
- "object_kind": "job",
+ "object_kind": "build",
"ref": "gitlab-script-trigger",
"tag": false,
"before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
"sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
- "job_id": 1977,
- "job_name": "test",
- "job_stage": "test",
- "job_status": "created",
- "job_started_at": null,
- "job_finished_at": null,
- "job_duration": null,
- "job_allow_failure": false,
- "job_failure_reason": "script_failure",
+ "build_id": 1977,
+ "build_name": "test",
+ "build_stage": "test",
+ "build_status": "created",
+ "build_started_at": null,
+ "build_finished_at": null,
+ "build_duration": null,
+ "build_allow_failure": false,
+ "build_failure_reason": "script_failure",
"project_id": 380,
"project_name": "gitlab-org/gitlab-test",
"user": {
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index d7d168710ef..41a7ed09281 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -192,7 +192,7 @@ You can also click the `+` to add more related issues.
#### 19. Related Merge Requests
-Merge requests that were mentioned in that issue's description or in the issue thread
+Merge requests that were mentioned in that issue's description or in the issue thread
are listed as [related merge requests](crosslinking_issues.md#from-merge-requests) here.
Also, if the current issue was mentioned as related in another merge request, that
merge request will be listed here.
diff --git a/doc/user/project/issues/sorting_issue_lists.md b/doc/user/project/issues/sorting_issue_lists.md
index 0fe86e6f410..6e31acf80bc 100644
--- a/doc/user/project/issues/sorting_issue_lists.md
+++ b/doc/user/project/issues/sorting_issue_lists.md
@@ -15,14 +15,14 @@ When you select **Manual** sorting, you can change
the order by dragging and dropping the issues. The changed order will persist. Everyone who visits the same list will see the reordered list, with some exceptions.
Each issue is assigned a relative order value, representing its relative
-order with respect to the other issues in the list. When you drag-and-drop reorder
+order with respect to the other issues in the list. When you drag-and-drop reorder
an issue, its relative order value changes accordingly.
In addition, any time that issue appears in a manually sorted list,
the updated relative order value will be used for the ordering. This means that
if issue `A` is drag-and-drop reordered to be above issue `B` by any user in
a given list inside your GitLab instance, any time those two issues are subsequently
-loaded in any list in the same instance (could be a different project issue list or a
+loaded in any list in the same instance (could be a different project issue list or a
different group issue list, for example), that ordering will be maintained.
This ordering also affects [issue boards](../issue_board.md#issue-ordering-in-a-list).
diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md
index e94125e658d..a88fb4fc6f8 100644
--- a/doc/user/project/merge_requests/allow_collaboration.md
+++ b/doc/user/project/merge_requests/allow_collaboration.md
@@ -4,8 +4,7 @@ type: reference, howto
# Allow collaboration on merge requests across forks
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395)
- in GitLab 10.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395) in GitLab 10.6.
When a user opens a merge request from a fork, they are given the option to allow
upstream members to collaborate with them on the source branch. This allows
diff --git a/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png b/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
index 32b9a3b9ce4..bbb131e86e9 100644
--- a/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
+++ b/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 04db54872d3..a94057dc3a1 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -41,7 +41,7 @@ With **[GitLab Enterprise Edition][ee]**, you can also:
- View the deployment process across projects with [Multi-Project Pipelines](../../../ci/multi_project_pipelines.md) **(PREMIUM)**
- Request [approvals](merge_request_approvals.md) from your managers **(STARTER)**
- Analyze the impact of your changes with [Code Quality reports](code_quality.md) **(STARTER)**
-- Manage the licenses of your dependencies with [License Compliance](../../application_security/license_management/index.md) **(ULTIMATE)**
+- Manage the licenses of your dependencies with [License Compliance](../../application_security/license_compliance/index.md) **(ULTIMATE)**
- Analyze your source code for vulnerabilities with [Static Application Security Testing](../../application_security/sast/index.md) **(ULTIMATE)**
- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](../../application_security/dast/index.md) **(ULTIMATE)**
- Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **(ULTIMATE)**
@@ -57,7 +57,7 @@ A. Consider you are a software developer working in a team:
1. You gather feedback from your team
1. You work on the implementation optimizing code with [Code Quality reports](code_quality.md) **(STARTER)**
1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD
-1. You avoid using dependencies whose license is not compatible with your project with [License Compliance reports](license_management.md) **(ULTIMATE)**
+1. You avoid using dependencies whose license is not compatible with your project with [License Compliance reports](../../application_security/license_compliance/index.md) **(ULTIMATE)**
1. You request the [approval](#merge-request-approvals-starter) from your manager
1. Your manager pushes a commit with their final review, [approves the merge request](merge_request_approvals.md), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#whenmanual) for GitLab CI/CD
diff --git a/doc/user/project/merge_requests/license_management.md b/doc/user/project/merge_requests/license_management.md
index 93116ebd7c6..df5bd073ade 100644
--- a/doc/user/project/merge_requests/license_management.md
+++ b/doc/user/project/merge_requests/license_management.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../../application_security/license_management/index.md'
+redirect_to: '../../application_security/license_compliance/index.md'
---
-This document was moved to [another location](../../application_security/license_management/index.md).
+This document was moved to [another location](../../application_security/license_compliance/index.md).
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index a0432414bcf..ca9ddd91e0d 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -330,7 +330,7 @@ the dropdown) `approver` and select the user.
Merge Request Approvals can be configured to require approval from a member
of your security team when a vulnerability would be introduced by a merge request.
-For more information, see
+For more information, see
[Security approvals in merge requests](../../application_security/index.md#security-approvals-in-merge-requests-ultimate).
<!-- ## Troubleshooting
diff --git a/doc/user/project/operations/feature_flags.md b/doc/user/project/operations/feature_flags.md
index 75b0623e6b0..6536a1a0a4b 100644
--- a/doc/user/project/operations/feature_flags.md
+++ b/doc/user/project/operations/feature_flags.md
@@ -85,7 +85,7 @@ NOTE: **NOTE**
We'd highly recommend you to use the [Environment](../../../ci/environments.md)
feature in order to quickly assess which flag is enabled per environment.
-## Rollout Strategy
+## Rollout strategy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8240) in GitLab 12.2.
@@ -97,33 +97,38 @@ However, a feature will be enabled for 50% of logged-in users if the matching en
### All users
-Enables the feature for all users.
-
-**All users** is implemented using the Unleash [default](https://unleash.github.io/docs/activation_strategy#default) activation strategy.
+Enables the feature for all users. It is implemented using the Unleash
+[`default`](https://unleash.github.io/docs/activation_strategy#default)
+activation strategy.
### Percent rollout (logged in users)
-**Percent rollout (logged in users)** enables the feature for a percentage of authenticated users. Set a value of 15%, for example, to enable the feature for 15% of authenticated users.
+Enables the feature for a percentage of authenticated users. It is
+implemented using the Unleash
+[`gradualRolloutUserId`](https://unleash.github.io/docs/activation_strategy#gradualrolloutuserid)
+activation strategy.
+
+Set a value of 15%, for example, to enable the feature for 15% of authenticated users.
A rollout percentage may be between 0% and 100%.
CAUTION: **Caution:**
-If this strategy is selected, then the Unleash client **must** be given a user id for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
+If this strategy is selected, then the Unleash client **must** be given a user
+ID for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
-**Percent rollout (logged in users)** is implemented using the Unleash [gradualRolloutUserId](https://unleash.github.io/docs/activation_strategy#gradualrolloutuserid) activation strategy.
-
-## Target Users
+## Target users
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8240) in GitLab 12.2.
-A feature flag may be enabled for a list of target users.
+A feature flag may be enabled for a list of target users. It is implemented
+using the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid)
+activation strategy.
![Feature flag target users](img/target_users_v12_2.png)
CAUTION: **Caution:**
-The Unleash client **must** be given a user id for the feature to be enabled for target users. See the [Ruby example](#ruby-application-example) below.
-
-**Target users** is implemented using the Unleash [userWithId](https://unleash.github.io/docs/activation_strategy#userwithid) activation strategy.
+The Unleash client **must** be given a user ID for the feature to be enabled for
+target users. See the [Ruby example](#ruby-application-example) below.
## Integrating with your application
diff --git a/doc/user/project/operations/tracing.md b/doc/user/project/operations/tracing.md
index b92d2e49839..3fb3be3c21f 100644
--- a/doc/user/project/operations/tracing.md
+++ b/doc/user/project/operations/tracing.md
@@ -17,8 +17,8 @@ systems.
### Deploying Jaeger
To learn more about deploying Jaeger, read the official
-[Getting Started documentation](https://www.jaegertracing.io/docs/1.13/getting-started/).
-There is an easy to use [all-in-one Docker image](https://www.jaegertracing.io/docs/1.13/getting-started/#AllinoneDockerimage),
+[Getting Started documentation](https://www.jaegertracing.io/docs/latest/getting-started/).
+There is an easy to use [all-in-one Docker image](https://www.jaegertracing.io/docs/latest/getting-started/#AllinoneDockerimage),
as well as deployment options for [Kubernetes](https://github.com/jaegertracing/jaeger-kubernetes)
and [OpenShift](https://github.com/jaegertracing/jaeger-openshift).
@@ -27,7 +27,7 @@ and [OpenShift](https://github.com/jaegertracing/jaeger-openshift).
GitLab provides an easy way to open the Jaeger UI from within your project:
1. [Set up Jaeger](#deploying-jaeger) and configure your application using one of the
- [client libraries](https://www.jaegertracing.io/docs/1.13/client-libraries/).
+ [client libraries](https://www.jaegertracing.io/docs/latest/client-libraries/).
1. Navigate to your project's **Settings > Operations** and provide the Jaeger URL.
1. Click **Save changes** for the changes to take effect.
1. You can now visit **Operations > Tracing** in your project's sidebar and
diff --git a/doc/user/project/packages/maven_repository.md b/doc/user/project/packages/maven_repository.md
index c61402afb2c..491ebc0c068 100644
--- a/doc/user/project/packages/maven_repository.md
+++ b/doc/user/project/packages/maven_repository.md
@@ -1,7 +1,6 @@
# GitLab Maven Repository **(PREMIUM)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5811) in
- [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5811) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
With the GitLab [Maven](https://maven.apache.org) Repository, every
project can have its own space to store its Maven artifacts.
diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md
index e01bac6b26f..48186688da9 100644
--- a/doc/user/project/packages/npm_registry.md
+++ b/doc/user/project/packages/npm_registry.md
@@ -1,7 +1,6 @@
# GitLab NPM Registry **(PREMIUM)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5934)
- in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
With the GitLab NPM Registry, every
project can have its own space to store NPM packages.
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
index 3b80370d4a9..dc75eb450a3 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
@@ -92,4 +92,4 @@ You can have one DNS record or more than one combined:
- `example.com` => `A` => `192.192.192.192`
- `www` => `CNAME` => `example.com`
- `MX` => `mail.example.com`
-- `example.com`=> TXT => `"google-site-verification=6P08Ow5E-8Q0m6vQ7FMAqAYIDprkVV8fUf_7hZ4Qvc8"` \ No newline at end of file
+- `example.com`=> TXT => `"google-site-verification=6P08Ow5E-8Q0m6vQ7FMAqAYIDprkVV8fUf_7hZ4Qvc8"`
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 6d538ca2455..45fdab1ca3a 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -100,4 +100,4 @@ _Read on about [Projects for GitLab Pages and URL structure](getting_started_par
- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
-- Fork an [example project](https://gitlab.com/pages) to build your website based upon \ No newline at end of file
+- Fork an [example project](https://gitlab.com/pages) to build your website based upon
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 456209c2180..78b6d202b32 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -107,11 +107,11 @@ in the pipelines settings page.
### Removing color codes
Some test coverage tools output with ANSI color codes that won't be
-parsed correctly by the regular expression and will cause coverage
-parsing to fail.
+parsed correctly by the regular expression and will cause coverage
+parsing to fail.
If your coverage tool doesn't provide an option to disable color
-codes in the output, you can pipe the output of the coverage tool through a
+codes in the output, you can pipe the output of the coverage tool through a
small one line script that will strip the color codes off.
For example:
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 7a79cdbcaee..8423b962948 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -34,11 +34,11 @@ that the `master` branch is protected by default.
1. From the **Branch** dropdown menu, select the branch you want to protect and
click **Protect**. In the screenshot below, we chose the `develop` branch.
- ![Protected branches page](img/protected_branches_page.png)
+ ![Protected branches page](img/protected_branches_page_v12_3.png)
1. Once done, the protected branch will appear in the "Protected branches" list.
- ![Protected branches list](img/protected_branches_list.png)
+ ![Protected branches list](img/protected_branches_list_v12_3.png)
## Using the Allowed to merge and Allowed to push settings
@@ -65,7 +65,7 @@ You can set the "Allowed to push" and "Allowed to merge" options while creating
a protected branch or afterwards by selecting the option you want from the
dropdown list in the "Already protected" area.
-![Developers can push](img/protected_branches_devs_can_push.png)
+![Developers can push](img/protected_branches_devs_can_push_v12_3.png)
If you don't choose any of those options while creating a protected branch,
they are set to "Maintainers" by default.
diff --git a/doc/user/project/protected_tags.md b/doc/user/project/protected_tags.md
index 5cc3b8a7fc3..9651c8824ab 100644
--- a/doc/user/project/protected_tags.md
+++ b/doc/user/project/protected_tags.md
@@ -24,15 +24,15 @@ To protect a tag, you need to have at least Maintainer permission level.
1. From the **Tag** dropdown menu, select the tag you want to protect or type and click **Create wildcard**. In the screenshot below, we chose to protect all tags matching `v*`:
- ![Protected tags page](img/protected_tags_page.png)
+ ![Protected tags page](img/protected_tags_page_v12_3.png)
1. From the **Allowed to create** dropdown, select who will have permission to create matching tags and then click **Protect**:
- ![Allowed to create tags dropdown](img/protected_tags_permissions_dropdown.png)
+ ![Allowed to create tags dropdown](img/protected_tags_permissions_dropdown_v12_3.png)
1. Once done, the protected tag will appear in the **Protected tags** list:
- ![Protected tags list](img/protected_tags_list.png)
+ ![Protected tags list](img/protected_tags_list_v12_3.png)
## Wildcard protected tags
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index bd966185c94..a838f06b2fd 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -73,7 +73,7 @@ according to the markup language.
| [Markdown](../../markdown.md) | `mdown`, `mkd`, `mkdn`, `md`, `markdown` |
| [reStructuredText](http://docutils.sourceforge.net/rst.html) | `rst` |
| [AsciiDoc](../../asciidoc.md) | `adoc`, `ad`, `asciidoc` |
-| [Textile](https://txstyle.org/) | `textile` |
+| [Textile](https://textile-lang.com/) | `textile` |
| [rdoc](http://rdoc.sourceforge.net/doc/index.html) | `rdoc` |
| [Orgmode](https://orgmode.org/) | `org` |
| [creole](http://www.wikicreole.org/) | `creole` |
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 4e3db95b6d6..58ccd8bf2ae 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -34,7 +34,7 @@ Issues or Merge Requests, both of which use Labels and Milestones, then you shou
#### Disabling email notifications
-You can disable all email notifications related to the project by selecting the
+You can disable all email notifications related to the project by selecting the
**Disable email notifications** checkbox. Only the project owner is allowed to change
this setting.
diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md
index 68532ccee65..45ece5f048e 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -14,27 +14,27 @@ For a list of words that are not allowed to be used as group or project names, s
It is currently not possible to create a project with the following names:
-- \-
-- badges
-- blame
-- blob
-- builds
-- commits
-- create
-- create_dir
-- edit
-- environments/folders
-- files
-- find_file
-- gitlab-lfs/objects
-- info/lfs/objects
-- new
-- preview
-- raw
-- refs
-- tree
-- update
-- wikis
+- `\-`
+- `badges`
+- `blame`
+- `blob`
+- `builds`
+- `commits`
+- `create`
+- `create_dir`
+- `edit`
+- `environments/folders`
+- `files`
+- `find_file`
+- `gitlab-lfs/objects`
+- `info/lfs/objects`
+- `new`
+- `preview`
+- `raw`
+- `refs`
+- `tree`
+- `update`
+- `wikis`
## Reserved group names
diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md
index c3d22e4fd29..d65dd32fe11 100644
--- a/doc/user/search/advanced_search_syntax.md
+++ b/doc/user/search/advanced_search_syntax.md
@@ -1,6 +1,7 @@
# Advanced Syntax Search **(STARTER ONLY)**
> **Notes:**
+>
> - Introduced in [GitLab Enterprise Starter][ee] 9.2
> - This is the user documentation. To install and configure Elasticsearch,
> visit the [administrator documentation](../../integration/elasticsearch.md).
diff --git a/doc/workflow/git_annex.md b/doc/workflow/git_annex.md
index cdc38a5b7eb..c3865aa953b 100644
--- a/doc/workflow/git_annex.md
+++ b/doc/workflow/git_annex.md
@@ -2,7 +2,7 @@
> **Warning:** GitLab has [completely
removed][deprecate-annex-issue] in GitLab 9.0 (2017/03/22).
-Read through the [migration guide from git-annex to git-lfs][guide].
+Read through the [migration guide from git-annex to Git LFS][guide].
The biggest limitation of Git, compared to some older centralized version
control systems, has been the maximum size of the repositories.
@@ -51,7 +51,7 @@ sudo yum install epel-release && sudo yum install git-annex
### Configuration for Omnibus packages
-For omnibus-gitlab packages, only one configuration setting is needed.
+For Omnibus GitLab packages, only one configuration setting is needed.
The Omnibus package will internally set the correct options in all locations.
1. In `/etc/gitlab/gitlab.rb` add the following line:
@@ -67,7 +67,7 @@ The Omnibus package will internally set the correct options in all locations.
There are 2 settings to enable git-annex on your GitLab server.
One is located in `config/gitlab.yml` of the GitLab repository and the other
-one is located in `config.yml` of gitlab-shell.
+one is located in `config.yml` of GitLab Shell.
1. In `config/gitlab.yml` add or edit the following lines:
@@ -76,7 +76,7 @@ one is located in `config.yml` of gitlab-shell.
git_annex_enabled: true
```
-1. In `config.yml` of gitlab-shell add or edit the following lines:
+1. In `config.yml` of GitLab Shell add or edit the following lines:
```yaml
git_annex_enabled: true
@@ -184,7 +184,7 @@ access files of projects you have access to (developer, maintainer, or owner rol
Internally GitLab uses [GitLab Shell] to handle SSH access and this was a great
integration point for `git-annex`.
-There is a setting in gitlab-shell so you can disable GitLab Annex support
+There is a setting in GitLab Shell so you can disable GitLab Annex support
if you want to.
## Troubleshooting tips
@@ -200,7 +200,7 @@ searching for your distribution.
Although there is no general guide for `git-annex` errors, there are a few tips
on how to go around the warnings.
-### git-annex-shell: Not a git-annex or gcrypt repository
+### `git-annex-shell: Not a git-annex or gcrypt repository`
This warning can appear on the initial `git annex sync --content` and is caused
by differences in `git-annex-shell`. You can read more about it
diff --git a/doc/workflow/issue_weight.md b/doc/workflow/issue_weight.md
index a80519f0748..291646a430e 100644
--- a/doc/workflow/issue_weight.md
+++ b/doc/workflow/issue_weight.md
@@ -1,7 +1,6 @@
-# Issue Weight **(STARTER)**
+# Issue weight **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/76)
-> in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/76) in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
When you have a lot of issues, it can be hard to get an overview.
By adding a weight to each issue, you can get a better idea of how much time,
diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md
index 00cddda24a4..1fd63a556c6 100644
--- a/doc/workflow/releases.md
+++ b/doc/workflow/releases.md
@@ -3,20 +3,20 @@
NOTE: In GitLab 11.7, we introduced the full fledged [Releases](../user/project/releases/index.md)
feature. You can still create release notes on this page, but the new method is preferred.
-You can add release notes to any git tag using the notes feature. Release notes
+You can add release notes to any Git tag using the notes feature. Release notes
behave like any other markdown form in GitLab so you can write text and
drag-n-drop files to it. Release notes are stored in GitLab's database.
There are several ways to add release notes:
-- In the interface, when you create a new git tag
-- In the interface, by adding a note to an existing git tag
+- In the interface, when you create a new Git tag
+- In the interface, by adding a note to an existing Git tag
- Using the GitLab API
## New tag page with release notes text area
![new_tag](releases/new_tag.png)
-## Tags page with button to add or edit release notes for existing git tag
+## Tags page with button to add or edit release notes for existing Git tag
![tags](releases/tags.png)
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index fad853f8a44..3d9f015b1fe 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -84,4 +84,4 @@ With this option enabled, `75h` is displayed instead of `1w 4d 3h`.
## Other interesting links
-- [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/solutions/time-tracking/)
+- [Time Tracking landing page in the GitLab handbook](https://about.gitlab.com/solutions/time-tracking/)
diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md
index 60a4d0f19de..4101f891484 100644
--- a/doc/workflow/timezone.md
+++ b/doc/workflow/timezone.md
@@ -17,7 +17,7 @@ For Omnibus installations, run `gitlab-rake time:zones:all`.
NOTE: **Note:**
Currently, this rake task does not list timezones in TZInfo format required by GitLab Omnibus during a reconfigure: [#58672](https://gitlab.com/gitlab-org/gitlab-ce/issues/58672).
-## Changing time zone in omnibus installations
+## Changing time zone in Omnibus installations
GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in `/etc/gitlab/gitlab.rb`.
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 6c1acc3963f..9125207167c 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -239,7 +239,7 @@ module API
# because notes are redacted if they point to projects that
# cannot be accessed by the user.
notes = prepare_notes_for_rendering(notes)
- notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes.select { |n| n.visible_for?(current_user) }
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 6f86adf6a5c..cfcf6228225 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1174,6 +1174,9 @@ module API
attributes.delete(:performance_bar_enabled)
attributes.delete(:allow_local_requests_from_hooks_and_services)
+ # let's not expose the secret key in a response
+ attributes.delete(:asset_proxy_secret_key)
+
attributes
end
@@ -1705,6 +1708,18 @@ module API
class ClusterGroup < Cluster
expose :group, using: Entities::BasicGroupDetails
end
+
+ module InternalPostReceive
+ class Message < Grape::Entity
+ expose :message
+ expose :type
+ end
+
+ class Response < Grape::Entity
+ expose :messages, using: Message
+ expose :reference_counter_decreased
+ end
+ end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index f545f33c06b..0bcd09d3977 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -216,6 +216,7 @@ module API
use :pagination
use :with_custom_attributes
+ use :optional_projects_params
end
get ":id/projects" do
projects = find_group_projects(params)
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 1aa6dc44bf7..5755f4b8d74 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -416,17 +416,7 @@ module API
# rubocop: enable CodeReuse/ActiveRecord
def project_finder_params
- finder_params = { without_deleted: true }
- finder_params[:owned] = true if params[:owned].present?
- finder_params[:non_public] = true if params[:membership].present?
- finder_params[:starred] = true if params[:starred].present?
- finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
- finder_params[:archived] = archived_param unless params[:archived].nil?
- finder_params[:search] = params[:search] if params[:search]
- finder_params[:user] = params.delete(:user) if params[:user]
- finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
- finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level]
- finder_params
+ project_finder_params_ce.merge(project_finder_params_ee)
end
# file helpers
@@ -461,6 +451,27 @@ module API
end
end
+ protected
+
+ def project_finder_params_ce
+ finder_params = { without_deleted: true }
+ finder_params[:owned] = true if params[:owned].present?
+ finder_params[:non_public] = true if params[:membership].present?
+ finder_params[:starred] = true if params[:starred].present?
+ finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
+ finder_params[:archived] = archived_param unless params[:archived].nil?
+ finder_params[:search] = params[:search] if params[:search]
+ finder_params[:user] = params.delete(:user) if params[:user]
+ finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
+ finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level]
+ finder_params
+ end
+
+ # Overridden in EE
+ def project_finder_params_ee
+ {}
+ end
+
private
# rubocop:disable Gitlab/ModuleWithInstanceVariables
diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb
index 2c33d79f6c8..6af12828ca5 100644
--- a/lib/api/helpers/groups_helpers.rb
+++ b/lib/api/helpers/groups_helpers.rb
@@ -28,6 +28,13 @@ module API
use :optional_params_ce
use :optional_params_ee
end
+
+ params :optional_projects_params_ee do
+ end
+
+ params :optional_projects_params do
+ use :optional_projects_params_ee
+ end
end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 9afe6c5b027..6b438235258 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -44,8 +44,6 @@ module API
end
def process_mr_push_options(push_options, project, user, changes)
- output = {}
-
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/61359')
service = ::MergeRequests::PushOptionsHandlerService.new(
@@ -56,15 +54,13 @@ module API
).execute
if service.errors.present?
- output[:warnings] = push_options_warning(service.errors.join("\n\n"))
+ push_options_warning(service.errors.join("\n\n"))
end
-
- output
end
def push_options_warning(warning)
options = Array.wrap(params[:push_options]).map { |p| "'#{p}'" }.join(' ')
- "Error encountered with push options #{options}: #{warning}"
+ "WARNINGS:\nError encountered with push options #{options}: #{warning}"
end
def redis_ping
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index b2bf6bf7417..f445834323d 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -12,7 +12,7 @@ module API
end
def update_note(noteable, note_id)
- note = noteable.notes.find(params[:note_id])
+ note = noteable.notes.find(note_id)
authorize! :admin_note, note
@@ -61,8 +61,8 @@ module API
end
def get_note(noteable, note_id)
- note = noteable.notes.with_metadata.find(params[:note_id])
- can_read_note = !note.cross_reference_not_visible_for?(current_user)
+ note = noteable.notes.with_metadata.find(note_id)
+ can_read_note = note.visible_for?(current_user)
if can_read_note
present note, with: Entities::Note
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 224aaaaf006..088ea5bd79a 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -256,25 +256,26 @@ module API
post '/post_receive' do
status 200
- output = {} # Messages to gitlab-shell
+ response = Gitlab::InternalPostReceive::Response.new
user = identify(params[:identifier])
project = Gitlab::GlRepository.parse(params[:gl_repository]).first
push_options = Gitlab::PushOptions.new(params[:push_options])
+ response.reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
+
PostReceive.perform_async(params[:gl_repository], params[:identifier],
params[:changes], push_options.as_json)
mr_options = push_options.get(:merge_request)
- output.merge!(process_mr_push_options(mr_options, project, user, params[:changes])) if mr_options.present?
+ if mr_options.present?
+ message = process_mr_push_options(mr_options, project, user, params[:changes])
+ response.add_alert_message(message)
+ end
broadcast_message = BroadcastMessage.current&.last&.message
- reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
+ response.add_alert_message(broadcast_message)
- output.merge!(
- broadcast_message: broadcast_message,
- reference_counter_decreased: reference_counter_decreased,
- merge_request_urls: merge_request_urls
- )
+ response.add_merge_request_urls(merge_request_urls)
# A user is not guaranteed to be returned; an orphaned write deploy
# key could be used
@@ -282,11 +283,11 @@ module API
redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)
project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id)
- output[:redirected_message] = redirect_message if redirect_message
- output[:project_created_message] = project_created_message if project_created_message
+ response.add_basic_message(redirect_message)
+ response.add_basic_message(project_created_message)
end
- output
+ present response, with: Entities::InternalPostReceive::Response
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 84563d66ee8..16fca9acccb 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -42,7 +42,7 @@ module API
# array returned, but this is really a edge-case.
notes = paginate(raw_notes)
notes = prepare_notes_for_rendering(notes)
- notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes = notes.select { |note| note.visible_for?(current_user) }
present notes, with: Entities::Note
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index a607df411a6..b4545295d54 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -51,16 +51,18 @@ module API
params do
requires :title, type: String, desc: 'The title of the snippet'
requires :file_name, type: String, desc: 'The file name of the snippet'
- requires :code, type: String, allow_blank: false, desc: 'The content of the snippet'
+ optional :code, type: String, allow_blank: false, desc: 'The content of the snippet (deprecated in favor of "content")'
+ optional :content, type: String, allow_blank: false, desc: 'The content of the snippet'
optional :description, type: String, desc: 'The description of a snippet'
requires :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
+ mutually_exclusive :code, :content
end
post ":id/snippets" do
authorize! :create_project_snippet, user_project
- snippet_params = declared_params.merge(request: request, api: true)
- snippet_params[:content] = snippet_params.delete(:code)
+ snippet_params = declared_params(include_missing: false).merge(request: request, api: true)
+ snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
@@ -80,12 +82,14 @@ module API
requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
optional :title, type: String, desc: 'The title of the snippet'
optional :file_name, type: String, desc: 'The file name of the snippet'
- optional :code, type: String, allow_blank: false, desc: 'The content of the snippet'
+ optional :code, type: String, allow_blank: false, desc: 'The content of the snippet (deprecated in favor of "content")'
+ optional :content, type: String, allow_blank: false, desc: 'The content of the snippet'
optional :description, type: String, desc: 'The description of a snippet'
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
- at_least_one_of :title, :file_name, :code, :visibility_level
+ at_least_one_of :title, :file_name, :code, :content, :visibility_level
+ mutually_exclusive :code, :content
end
# rubocop: disable CodeReuse/ActiveRecord
put ":id/snippets/:snippet_id" do
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index c36ee5af63f..dd27ebab83d 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -36,6 +36,10 @@ module API
given akismet_enabled: ->(val) { val } do
requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com'
end
+ optional :asset_proxy_enabled, type: Boolean, desc: 'Enable proxying of assets'
+ optional :asset_proxy_url, type: String, desc: 'URL of the asset proxy server'
+ optional :asset_proxy_secret_key, type: String, desc: 'Shared secret with the asset proxy server'
+ optional :asset_proxy_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted.'
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
@@ -104,6 +108,11 @@ module API
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
end
+ optional :login_recaptcha_protection_enabled, type: Boolean, desc: 'Helps prevent brute-force attacks'
+ given login_recaptcha_protection_enabled: ->(val) { val } do
+ requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
+ requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
+ end
optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects'
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to set up Two-factor authentication'
@@ -123,7 +132,7 @@ module API
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
- optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated"
+ optional :local_markdown_version, type: Integer, desc: 'Local markdown version, increase this value when any cached markdown should be invalidated'
optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5
optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking'
given snowplow_enabled: ->(val) { val } do
diff --git a/lib/api/validations/types/comma_separated_to_array.rb b/lib/api/validations/types/comma_separated_to_array.rb
new file mode 100644
index 00000000000..b551878abd1
--- /dev/null
+++ b/lib/api/validations/types/comma_separated_to_array.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module API
+ module Validations
+ module Types
+ class CommaSeparatedToArray
+ def self.coerce
+ lambda do |value|
+ case value
+ when String
+ value.split(',').map(&:strip)
+ when Array
+ value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
+ else
+ []
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 52af28ce8ec..a0439089879 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -7,6 +7,14 @@ module Banzai
class AbstractReferenceFilter < ReferenceFilter
include CrossProjectReference
+ # REFERENCE_PLACEHOLDER is used for re-escaping HTML text except found
+ # reference (which we replace with placeholder during re-scaping). The
+ # random number helps ensure it's pretty close to unique. Since it's a
+ # transitory value (it never gets saved) we can initialize once, and it
+ # doesn't matter if it changes on a restart.
+ REFERENCE_PLACEHOLDER = "_reference_#{SecureRandom.hex(16)}_"
+ REFERENCE_PLACEHOLDER_PATTERN = %r{#{REFERENCE_PLACEHOLDER}(\d+)}.freeze
+
def self.object_class
# Implement in child class
# Example: MergeRequest
@@ -389,6 +397,14 @@ module Banzai
def escape_html_entities(text)
CGI.escapeHTML(text.to_s)
end
+
+ def escape_with_placeholders(text, placeholder_data)
+ escaped = escape_html_entities(text)
+
+ escaped.gsub(REFERENCE_PLACEHOLDER_PATTERN) do |match|
+ placeholder_data[$1.to_i]
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/asset_proxy_filter.rb b/lib/banzai/filter/asset_proxy_filter.rb
new file mode 100644
index 00000000000..0a9a52a73a1
--- /dev/null
+++ b/lib/banzai/filter/asset_proxy_filter.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # Proxy's images/assets to another server. Reduces mixed content warnings
+ # as well as hiding the customer's IP address when requesting images.
+ # Copies the original img `src` to `data-canonical-src` then replaces the
+ # `src` with a new url to the proxy server.
+ class AssetProxyFilter < HTML::Pipeline::CamoFilter
+ def initialize(text, context = nil, result = nil)
+ super
+ end
+
+ def validate
+ needs(:asset_proxy, :asset_proxy_secret_key) if asset_proxy_enabled?
+ end
+
+ def asset_host_whitelisted?(host)
+ context[:asset_proxy_domain_regexp] ? context[:asset_proxy_domain_regexp].match?(host) : false
+ end
+
+ def self.transform_context(context)
+ context[:disable_asset_proxy] = !Gitlab.config.asset_proxy.enabled
+
+ unless context[:disable_asset_proxy]
+ context[:asset_proxy_enabled] = !context[:disable_asset_proxy]
+ context[:asset_proxy] = Gitlab.config.asset_proxy.url
+ context[:asset_proxy_secret_key] = Gitlab.config.asset_proxy.secret_key
+ context[:asset_proxy_domain_regexp] = Gitlab.config.asset_proxy.domain_regexp
+ end
+
+ context
+ end
+
+ # called during an initializer. Caching the values in Gitlab.config significantly increased
+ # performance, rather than querying Gitlab::CurrentSettings.current_application_settings
+ # over and over. However, this does mean that the Rails servers need to get restarted
+ # whenever the application settings are changed
+ def self.initialize_settings
+ application_settings = Gitlab::CurrentSettings.current_application_settings
+ Gitlab.config['asset_proxy'] ||= Settingslogic.new({})
+
+ if application_settings.respond_to?(:asset_proxy_enabled)
+ Gitlab.config.asset_proxy['enabled'] = application_settings.asset_proxy_enabled
+ Gitlab.config.asset_proxy['url'] = application_settings.asset_proxy_url
+ Gitlab.config.asset_proxy['secret_key'] = application_settings.asset_proxy_secret_key
+ Gitlab.config.asset_proxy['whitelist'] = application_settings.asset_proxy_whitelist || [Gitlab.config.gitlab.host]
+ Gitlab.config.asset_proxy['domain_regexp'] = compile_whitelist(Gitlab.config.asset_proxy.whitelist)
+ else
+ Gitlab.config.asset_proxy['enabled'] = ::ApplicationSetting.defaults[:asset_proxy_enabled]
+ end
+ end
+
+ def self.compile_whitelist(domain_list)
+ return if domain_list.empty?
+
+ escaped = domain_list.map { |domain| Regexp.escape(domain).gsub('\*', '.*?') }
+ Regexp.new("^(#{escaped.join('|')})$", Regexp::IGNORECASE)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
index 61ee3eac216..fb721fe12b1 100644
--- a/lib/banzai/filter/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -14,10 +14,10 @@ module Banzai
# such as on `mailto:` links. Since we've been using it, do an
# initial parse for validity and then use Addressable
# for IDN support, etc
- uri = uri_strict(node['href'].to_s)
+ uri = uri_strict(node_src(node))
if uri
- node.set_attribute('href', uri.to_s)
- addressable_uri = addressable_uri(node['href'])
+ node.set_attribute(node_src_attribute(node), uri.to_s)
+ addressable_uri = addressable_uri(node_src(node))
else
addressable_uri = nil
end
@@ -35,6 +35,16 @@ module Banzai
private
+ # if this is a link to a proxied image, then `src` is already the correct
+ # proxied url, so work with the `data-canonical-src`
+ def node_src_attribute(node)
+ node['data-canonical-src'] ? 'data-canonical-src' : 'href'
+ end
+
+ def node_src(node)
+ node[node_src_attribute(node)]
+ end
+
def uri_strict(href)
URI.parse(href)
rescue URI::Error
@@ -72,7 +82,7 @@ module Banzai
return unless uri
return unless context[:emailable_links]
- unencoded_uri_str = Addressable::URI.unencode(node['href'])
+ unencoded_uri_str = Addressable::URI.unencode(node_src(node))
if unencoded_uri_str == node.content && idn?(uri)
node.content = uri.normalize
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index 01237303c27..ed0a01e6277 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -18,6 +18,9 @@ module Banzai
rel: 'noopener noreferrer'
)
+ # make sure the original non-proxied src carries over to the link
+ link['data-canonical-src'] = img['data-canonical-src'] if img['data-canonical-src']
+
link.children = img.clone
img.replace(link)
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 4892668fc22..a0789b7ca06 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -14,24 +14,24 @@ module Banzai
find_labels(parent_object).find(id)
end
- def self.references_in(text, pattern = Label.reference_pattern)
- unescape_html_entities(text).gsub(pattern) do |match|
- yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~[:namespace], $~
- end
- end
-
def references_in(text, pattern = Label.reference_pattern)
- unescape_html_entities(text).gsub(pattern) do |match|
+ labels = {}
+ unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
namespace, project = $~[:namespace], $~[:project]
project_path = full_project_path(namespace, project)
label = find_label(project_path, $~[:label_id], $~[:label_name])
if label
- yield match, label.id, project, namespace, $~
+ labels[label.id] = yield match, label.id, project, namespace, $~
+ "#{REFERENCE_PLACEHOLDER}#{label.id}"
else
- escape_html_entities(match)
+ match
end
end
+
+ return text if labels.empty?
+
+ escape_with_placeholders(unescaped_html, labels)
end
def find_label(parent_ref, label_id, label_name)
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index 08969753d75..4c47ee4dba1 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -51,15 +51,21 @@ module Banzai
# default implementation.
return super(text, pattern) if pattern != Milestone.reference_pattern
- unescape_html_entities(text).gsub(pattern) do |match|
+ milestones = {}
+ unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
if milestone
- yield match, milestone.id, $~[:project], $~[:namespace], $~
+ milestones[milestone.id] = yield match, milestone.id, $~[:project], $~[:namespace], $~
+ "#{REFERENCE_PLACEHOLDER}#{milestone.id}"
else
- escape_html_entities(match)
+ match
end
end
+
+ return text if milestones.empty?
+
+ escape_with_placeholders(unescaped_html, milestones)
end
def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 86f18679496..846a7d46aad 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -9,6 +9,7 @@ module Banzai
# Context options:
# :commit
# :group
+ # :current_user
# :project
# :project_wiki
# :ref
@@ -18,6 +19,7 @@ module Banzai
def call
return doc if context[:system_note]
+ return doc unless visible_to_user?
@uri_types = {}
clear_memoization(:linkable_files)
@@ -166,6 +168,16 @@ module Banzai
Gitlab.config.gitlab.relative_url_root.presence || '/'
end
+ def visible_to_user?
+ if project
+ Ability.allowed?(current_user, :download_code, project)
+ elsif group
+ Ability.allowed?(current_user, :read_group, group)
+ else # Objects detached from projects or groups, e.g. Personal Snippets.
+ true
+ end
+ end
+
def ref
context[:ref] || project.default_branch
end
@@ -178,6 +190,10 @@ module Banzai
context[:project]
end
+ def current_user
+ context[:current_user]
+ end
+
def repository
@repository ||= project&.repository
end
diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb
index 0fff104cf91..a278fcfdb47 100644
--- a/lib/banzai/filter/video_link_filter.rb
+++ b/lib/banzai/filter/video_link_filter.rb
@@ -23,6 +23,14 @@ module Banzai
"'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
end
+ if context[:asset_proxy_enabled].present?
+ src_query.concat(
+ UploaderHelper::VIDEO_EXT.map do |ext|
+ "'.#{ext}' = substring(@data-canonical-src, string-length(@data-canonical-src) - #{ext.size})"
+ end
+ )
+ end
+
"descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]"
end
end
@@ -48,6 +56,13 @@ module Banzai
target: '_blank',
rel: 'noopener noreferrer',
title: "Download '#{element['title'] || element['alt']}'")
+
+ # make sure the original non-proxied src carries over
+ if element['data-canonical-src']
+ video['data-canonical-src'] = element['data-canonical-src']
+ link['data-canonical-src'] = element['data-canonical-src']
+ end
+
download_paragraph = doc.document.create_element('p')
download_paragraph.children = link
diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb
index d25b74b23b2..82b99d3de4a 100644
--- a/lib/banzai/pipeline/ascii_doc_pipeline.rb
+++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb
@@ -6,12 +6,17 @@ module Banzai
def self.filters
FilterArray[
Filter::AsciiDocSanitizationFilter,
+ Filter::AssetProxyFilter,
Filter::SyntaxHighlightFilter,
Filter::ExternalLinkFilter,
Filter::PlantumlFilter,
Filter::AsciiDocPostProcessingFilter
]
end
+
+ def self.transform_context(context)
+ Filter::AssetProxyFilter.transform_context(context)
+ end
end
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 2c1006f708a..f419e54c264 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -17,6 +17,7 @@ module Banzai
Filter::SpacedLinkFilter,
Filter::SanitizationFilter,
+ Filter::AssetProxyFilter,
Filter::SyntaxHighlightFilter,
Filter::MathFilter,
@@ -60,7 +61,7 @@ module Banzai
def self.transform_context(context)
context[:only_path] = true unless context.key?(:only_path)
- context
+ Filter::AssetProxyFilter.transform_context(context)
end
end
end
diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb
index ceba082cd4f..c86d5f08ded 100644
--- a/lib/banzai/pipeline/markup_pipeline.rb
+++ b/lib/banzai/pipeline/markup_pipeline.rb
@@ -6,11 +6,16 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::SanitizationFilter,
+ Filter::AssetProxyFilter,
Filter::ExternalLinkFilter,
Filter::PlantumlFilter,
Filter::SyntaxHighlightFilter
]
end
+
+ def self.transform_context(context)
+ Filter::AssetProxyFilter.transform_context(context)
+ end
end
end
end
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
index 72374207a8f..9aff6880f56 100644
--- a/lib/banzai/pipeline/single_line_pipeline.rb
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
@filters ||= FilterArray[
Filter::HtmlEntityFilter,
Filter::SanitizationFilter,
+ Filter::AssetProxyFilter,
Filter::EmojiFilter,
Filter::AutolinkFilter,
@@ -29,6 +30,8 @@ module Banzai
end
def self.transform_context(context)
+ context = Filter::AssetProxyFilter.transform_context(context)
+
super(context).merge(
no_sourcepos: true
)
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index d9d8dcf7900..e8b938e46b1 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -55,7 +55,7 @@ module Gitlab
SUBDOMAIN_REGEX === Gitlab.config.gitlab.url
end
- def self.dev_env_or_com?
+ def self.dev_env_org_or_com?
Rails.env.development? || org? || com?
end
diff --git a/lib/gitlab/anonymous_session.rb b/lib/gitlab/anonymous_session.rb
new file mode 100644
index 00000000000..148b6d3310d
--- /dev/null
+++ b/lib/gitlab/anonymous_session.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class AnonymousSession
+ def initialize(remote_ip, session_id: nil)
+ @remote_ip = remote_ip
+ @session_id = session_id
+ end
+
+ def store_session_id_per_ip
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.pipelined do
+ redis.sadd(session_lookup_name, session_id)
+ redis.expire(session_lookup_name, 24.hours)
+ end
+ end
+ end
+
+ def stored_sessions
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.scard(session_lookup_name)
+ end
+ end
+
+ def cleanup_session_per_ip_entries
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.srem(session_lookup_name, session_id)
+ end
+ end
+
+ private
+
+ attr_reader :remote_ip, :session_id
+
+ def session_lookup_name
+ @session_lookup_name ||= "#{Gitlab::Redis::SharedState::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
+ end
+ end
+end
diff --git a/lib/gitlab/authorized_keys.rb b/lib/gitlab/authorized_keys.rb
index 3fe72f5fd43..820a78b653c 100644
--- a/lib/gitlab/authorized_keys.rb
+++ b/lib/gitlab/authorized_keys.rb
@@ -13,6 +13,24 @@ module Gitlab
@logger = logger
end
+ # Checks if the file is accessible or not
+ #
+ # @return [Boolean]
+ def accessible?
+ open_authorized_keys_file('r') { true }
+ rescue Errno::ENOENT, Errno::EACCES
+ false
+ end
+
+ # Creates the authorized_keys file if it doesn't exist
+ #
+ # @return [Boolean]
+ def create
+ open_authorized_keys_file(File::CREAT) { true }
+ rescue Errno::EACCES
+ false
+ end
+
# Add id and its key to the authorized_keys file
#
# @param [String] id identifier of key prefixed by `key-`
@@ -102,10 +120,14 @@ module Gitlab
[]
end
+ def file
+ @file ||= Gitlab.config.gitlab_shell.authorized_keys_file
+ end
+
private
def lock(timeout = 10)
- File.open("#{authorized_keys_file}.lock", "w+") do |f|
+ File.open("#{file}.lock", "w+") do |f|
f.flock File::LOCK_EX
Timeout.timeout(timeout) { yield }
ensure
@@ -114,7 +136,7 @@ module Gitlab
end
def open_authorized_keys_file(mode)
- File.open(authorized_keys_file, mode, 0o600) do |file|
+ File.open(file, mode, 0o600) do |file|
file.chmod(0o600)
yield file
end
@@ -141,9 +163,5 @@ module Gitlab
def strip(key)
key.split(/[ ]+/)[0, 2].join(' ')
end
-
- def authorized_keys_file
- Gitlab.config.gitlab_shell.authorized_keys_file
- end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/limit/job_activity.rb b/lib/gitlab/ci/pipeline/chain/limit/job_activity.rb
new file mode 100644
index 00000000000..31c218bf954
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/limit/job_activity.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Limit
+ class JobActivity < Chain::Base
+ def perform!
+ # to be overridden in EE
+ end
+
+ def break?
+ false # to be overridden in EE
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index a12bbededc4..6ecd506d55b 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -13,6 +13,10 @@ module Gitlab
# FIXME: this should just be the max value of timestampz
MAX_TIMESTAMP_VALUE = Time.at((1 << 31) - 1).freeze
+ # The maximum number of characters for text fields, to avoid DoS attacks via parsing huge text fields
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/61974
+ MAX_TEXT_SIZE_LIMIT = 1_000_000
+
# Minimum schema version from which migrations are supported
# Migrations before this version may have been removed
MIN_SCHEMA_VERSION = 20190506135400
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 201db9fec26..d65c0d3e78d 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -50,7 +50,7 @@ module Gitlab
def self.interceptors
return [] unless Labkit::Tracing.enabled?
- [Labkit::Tracing::GRPCInterceptor.instance]
+ [Labkit::Tracing::GRPC::ClientInterceptor.instance]
end
private_class_method :interceptors
diff --git a/lib/gitlab/internal_post_receive/response.rb b/lib/gitlab/internal_post_receive/response.rb
new file mode 100644
index 00000000000..7e7ec2aa45c
--- /dev/null
+++ b/lib/gitlab/internal_post_receive/response.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module InternalPostReceive
+ class Response
+ attr_accessor :reference_counter_decreased
+ attr_reader :messages
+
+ Message = Struct.new(:message, :type) do
+ def self.basic(text)
+ new(text, :basic)
+ end
+
+ def self.alert(text)
+ new(text, :alert)
+ end
+ end
+
+ def initialize
+ @messages = []
+ @reference_counter_decreased = false
+ end
+
+ def add_merge_request_urls(urls_data)
+ urls_data.each do |url_data|
+ add_merge_request_url(url_data)
+ end
+ end
+
+ def add_merge_request_url(url_data)
+ message = if url_data[:new_merge_request]
+ "To create a merge request for #{url_data[:branch_name]}, visit:"
+ else
+ "View merge request for #{url_data[:branch_name]}:"
+ end
+
+ message += "\n #{url_data[:url]}"
+
+ add_basic_message(message)
+ end
+
+ def add_basic_message(text)
+ @messages << Message.basic(text) if text.present?
+ end
+
+ def add_alert_message(text)
+ @messages.unshift(Message.alert(text)) if text.present?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/jira/http_client.rb b/lib/gitlab/jira/http_client.rb
new file mode 100644
index 00000000000..11a33a7b358
--- /dev/null
+++ b/lib/gitlab/jira/http_client.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Jira
+ # Gitlab JIRA HTTP client to be used with jira-ruby gem, this subclasses JIRA::HTTPClient.
+ # Uses Gitlab::HTTP to make requests to JIRA REST API.
+ # The parent class implementation can be found at: https://github.com/sumoheavy/jira-ruby/blob/v1.4.0/lib/jira/http_client.rb
+ class HttpClient < JIRA::HttpClient
+ extend ::Gitlab::Utils::Override
+
+ override :request
+ def request(*args)
+ result = make_request(*args)
+
+ raise JIRA::HTTPError.new(result) unless result.response.is_a?(Net::HTTPSuccess)
+
+ result
+ end
+
+ override :make_cookie_auth_request
+ def make_cookie_auth_request
+ body = {
+ username: @options.delete(:username),
+ password: @options.delete(:password)
+ }.to_json
+
+ make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, { 'Content-Type' => 'application/json' })
+ end
+
+ override :make_request
+ def make_request(http_method, path, body = '', headers = {})
+ request_params = { headers: headers }
+ request_params[:body] = body if body.present?
+ request_params[:headers][:Cookie] = get_cookies if options[:use_cookies]
+ request_params[:timeout] = options[:read_timeout] if options[:read_timeout]
+ request_params[:base_uri] = uri.to_s
+ request_params.merge!(auth_params)
+
+ result = Gitlab::HTTP.public_send(http_method, path, **request_params) # rubocop:disable GitlabSecurity/PublicSend
+ @authenticated = result.response.is_a?(Net::HTTPOK)
+ store_cookies(result) if options[:use_cookies]
+
+ result
+ end
+
+ def auth_params
+ return {} unless @options[:username] && @options[:password]
+
+ {
+ basic_auth: {
+ username: @options[:username],
+ password: @options[:password]
+ }
+ }
+ end
+
+ private
+
+ def get_cookies
+ cookie_array = @cookies.values.map { |cookie| "#{cookie.name}=#{cookie.value[0]}" }
+ cookie_array += Array(@options[:additional_cookies]) if @options.key?(:additional_cookies)
+ cookie_array.join('; ') if cookie_array.any?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown_cache.rb b/lib/gitlab/markdown_cache.rb
index 0354c710a3f..03a2f62cbd9 100644
--- a/lib/gitlab/markdown_cache.rb
+++ b/lib/gitlab/markdown_cache.rb
@@ -3,8 +3,8 @@
module Gitlab
module MarkdownCache
# Increment this number every time the renderer changes its output
+ CACHE_COMMONMARK_VERSION = 17
CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 16
BaseError = Class.new(StandardError)
UnsupportedClassError = Class.new(BaseError)
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index f96466b2b00..d9c28ff1181 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -132,7 +132,7 @@ module Gitlab
NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze
NAMESPACE_FORMAT_REGEX = /(?:#{NAMESPACE_FORMAT_REGEX_JS})#{NO_SUFFIX_REGEX}/.freeze
PROJECT_PATH_FORMAT_REGEX = /(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX}/.freeze
- FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/)*#{NAMESPACE_FORMAT_REGEX}}.freeze
+ FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/){,#{Namespace::NUMBER_OF_ANCESTORS_ALLOWED}}#{NAMESPACE_FORMAT_REGEX}}.freeze
def root_namespace_route_regex
@root_namespace_route_regex ||= begin
diff --git a/lib/gitlab/performance_bar/with_top_level_warnings.rb b/lib/gitlab/performance_bar/with_top_level_warnings.rb
new file mode 100644
index 00000000000..fb5c5c5959d
--- /dev/null
+++ b/lib/gitlab/performance_bar/with_top_level_warnings.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PerformanceBar
+ module WithTopLevelWarnings
+ def results
+ results = super
+
+ results.merge(has_warnings: has_warnings?(results))
+ end
+
+ def has_warnings?(results)
+ results[:data].any? do |_, value|
+ value[:warnings].present?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb
index 772d743c9b0..f3cbe1db901 100644
--- a/lib/gitlab/recaptcha.rb
+++ b/lib/gitlab/recaptcha.rb
@@ -3,7 +3,7 @@
module Gitlab
module Recaptcha
def self.load_configurations!
- if Gitlab::CurrentSettings.recaptcha_enabled
+ if Gitlab::CurrentSettings.recaptcha_enabled || enabled_on_login?
::Recaptcha.configure do |config|
config.site_key = Gitlab::CurrentSettings.recaptcha_site_key
config.secret_key = Gitlab::CurrentSettings.recaptcha_private_key
@@ -16,5 +16,9 @@ module Gitlab
def self.enabled?
Gitlab::CurrentSettings.recaptcha_enabled
end
+
+ def self.enabled_on_login?
+ Gitlab::CurrentSettings.login_recaptcha_protection_enabled
+ end
end
end
diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb
index 9066606ca21..270a19e780c 100644
--- a/lib/gitlab/redis/shared_state.rb
+++ b/lib/gitlab/redis/shared_state.rb
@@ -9,6 +9,7 @@ module Gitlab
SESSION_NAMESPACE = 'session:gitlab'.freeze
USER_SESSIONS_NAMESPACE = 'session:user:gitlab'.freeze
USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'.freeze
+ IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab'.freeze
DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'.freeze
REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'.freeze
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
index 75503ee1789..e40c366ed02 100644
--- a/lib/gitlab/repository_cache_adapter.rb
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -23,37 +23,6 @@ module Gitlab
end
end
- # Caches and strongly memoizes the method as a Redis Set.
- #
- # This only works for methods that do not take any arguments. The method
- # should return an Array of Strings to be cached.
- #
- # In addition to overriding the named method, a "name_include?" method is
- # defined. This uses the "SISMEMBER" query to efficiently check membership
- # without needing to load the entire set into memory.
- #
- # name - The name of the method to be cached.
- # fallback - A value to fall back to if the repository does not exist, or
- # in case of a Git error. Defaults to nil.
- def cache_method_as_redis_set(name, fallback: nil)
- uncached_name = alias_uncached_method(name)
-
- define_method(name) do
- cache_method_output_as_redis_set(name, fallback: fallback) do
- __send__(uncached_name) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
-
- define_method("#{name}_include?") do |value|
- # If the cache isn't populated, we can't rely on it
- return redis_set_cache.include?(name, value) if redis_set_cache.exist?(name)
-
- # Since we have to pull all branch names to populate the cache, use
- # the data we already have to answer the query just this once
- __send__(name).include?(value) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
-
# Caches truthy values from the method. All values are strongly memoized,
# and cached in RequestStore.
#
@@ -115,11 +84,6 @@ module Gitlab
raise NotImplementedError
end
- # RepositorySetCache to be used. Should be overridden by the including class
- def redis_set_cache
- raise NotImplementedError
- end
-
# List of cached methods. Should be overridden by the including class
def cached_methods
raise NotImplementedError
@@ -136,18 +100,6 @@ module Gitlab
end
end
- # Caches and strongly memoizes the supplied block as a Redis Set. The result
- # will be provided as a sorted array.
- #
- # name - The name of the method to be cached.
- # fallback - A value to fall back to if the repository does not exist, or
- # in case of a Git error. Defaults to nil.
- def cache_method_output_as_redis_set(name, fallback: nil, &block)
- memoize_method_output(name, fallback: fallback) do
- redis_set_cache.fetch(name, &block).sort
- end
- end
-
# Caches truthy values from the supplied block. All values are strongly
# memoized, and cached in RequestStore.
#
@@ -202,7 +154,6 @@ module Gitlab
clear_memoization(memoizable_name(name))
end
- expire_redis_set_method_caches(methods)
expire_request_store_method_caches(methods)
end
@@ -218,10 +169,6 @@ module Gitlab
end
end
- def expire_redis_set_method_caches(methods)
- methods.each { |name| redis_set_cache.expire(name) }
- end
-
# All cached repository methods depend on the existence of a Git repository,
# so if the repository doesn't exist, we already know not to call it.
def fallback_early?(method_name)
diff --git a/lib/gitlab/repository_set_cache.rb b/lib/gitlab/repository_set_cache.rb
deleted file mode 100644
index fb634328a95..00000000000
--- a/lib/gitlab/repository_set_cache.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-# Interface to the Redis-backed cache store for keys that use a Redis set
-module Gitlab
- class RepositorySetCache
- attr_reader :repository, :namespace, :expires_in
-
- def initialize(repository, extra_namespace: nil, expires_in: 2.weeks)
- @repository = repository
- @namespace = "#{repository.full_path}:#{repository.project.id}"
- @namespace = "#{@namespace}:#{extra_namespace}" if extra_namespace
- @expires_in = expires_in
- end
-
- def cache_key(type)
- [type, namespace, 'set'].join(':')
- end
-
- def expire(key)
- with { |redis| redis.del(cache_key(key)) }
- end
-
- def exist?(key)
- with { |redis| redis.exists(cache_key(key)) }
- end
-
- def read(key)
- with { |redis| redis.smembers(cache_key(key)) }
- end
-
- def write(key, value)
- full_key = cache_key(key)
-
- with do |redis|
- redis.multi do
- redis.del(full_key)
-
- # Splitting into groups of 1000 prevents us from creating a too-long
- # Redis command
- value.in_groups_of(1000, false) { |subset| redis.sadd(full_key, subset) }
-
- redis.expire(full_key, expires_in)
- end
- end
-
- value
- end
-
- def fetch(key, &block)
- if exist?(key)
- read(key)
- else
- write(key, yield)
- end
- end
-
- def include?(key, value)
- with { |redis| redis.sismember(cache_key(key), value) }
- end
-
- private
-
- def with(&blk)
- Gitlab::Redis::Cache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
- end
- end
-end
diff --git a/lib/gitlab/sanitizers/exif.rb b/lib/gitlab/sanitizers/exif.rb
index bb4e4ce7bbc..2f3d14ecebd 100644
--- a/lib/gitlab/sanitizers/exif.rb
+++ b/lib/gitlab/sanitizers/exif.rb
@@ -53,15 +53,18 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil)
+ def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil, uploader: nil, since: nil)
relation = Upload.where('lower(path) like ? or lower(path) like ? or lower(path) like ?',
'%.jpg', '%.jpeg', '%.tiff')
+ relation = relation.where(uploader: uploader) if uploader
+ relation = relation.where('created_at > ?', since) if since
logger.info "running in dry run mode, no images will be rewritten" if dry_run
find_params = {
start: start_id.present? ? start_id.to_i : nil,
- finish: stop_id.present? ? stop_id.to_i : Upload.last&.id
+ finish: stop_id.present? ? stop_id.to_i : Upload.last&.id,
+ batch_size: 1000
}
relation.find_each(find_params) do |upload|
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 0fa17b3f559..9e813968093 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -165,16 +165,7 @@ module Gitlab
def add_key(key_id, key_content)
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- gitlab_shell_fast_execute([
- gitlab_shell_keys_path,
- 'add-key',
- key_id,
- strip_key(key_content)
- ])
- else
- gitlab_authorized_keys.add_key(key_id, key_content)
- end
+ gitlab_authorized_keys.add_key(key_id, key_content)
end
# Batch-add keys to authorized_keys
@@ -184,19 +175,7 @@ module Gitlab
def batch_add_keys(keys)
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- begin
- IO.popen("#{gitlab_shell_keys_path} batch-add-keys", 'w') do |io|
- add_keys_to_io(keys, io)
- end
-
- $?.success?
- rescue Error
- false
- end
- else
- gitlab_authorized_keys.batch_add_keys(keys)
- end
+ gitlab_authorized_keys.batch_add_keys(keys)
end
# Remove ssh key from authorized_keys
@@ -207,11 +186,7 @@ module Gitlab
def remove_key(id, _ = nil)
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- gitlab_shell_fast_execute([gitlab_shell_keys_path, 'rm-key', id])
- else
- gitlab_authorized_keys.rm_key(id)
- end
+ gitlab_authorized_keys.rm_key(id)
end
# Remove all ssh keys from gitlab shell
@@ -222,11 +197,7 @@ module Gitlab
def remove_all_keys
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
- else
- gitlab_authorized_keys.clear
- end
+ gitlab_authorized_keys.clear
end
# Remove ssh keys from gitlab shell that are not in the DB
@@ -341,14 +312,6 @@ module Gitlab
File.join(Gitlab.config.repositories.storages[storage].legacy_disk_path, dir_name)
end
- def gitlab_shell_projects_path
- File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
- end
-
- def gitlab_shell_keys_path
- File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
- end
-
def authorized_keys_enabled?
# Return true if nil to ensure the authorized_keys methods work while
# fixing the authorized_keys file during migration.
@@ -359,35 +322,6 @@ module Gitlab
private
- def shell_out_for_gitlab_keys?
- Gitlab.config.gitlab_shell.authorized_keys_file.blank?
- end
-
- def gitlab_shell_fast_execute(cmd)
- output, status = gitlab_shell_fast_execute_helper(cmd)
-
- return true if status.zero?
-
- Rails.logger.error("gitlab-shell failed with error #{status}: #{output}") # rubocop:disable Gitlab/RailsLogger
- false
- end
-
- def gitlab_shell_fast_execute_raise_error(cmd, vars = {})
- output, status = gitlab_shell_fast_execute_helper(cmd, vars)
-
- raise Error, output unless status.zero?
-
- true
- end
-
- def gitlab_shell_fast_execute_helper(cmd, vars = {})
- vars.merge!(ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS))
-
- # Don't pass along the entire parent environment to prevent gitlab-shell
- # from wasting I/O by searching through GEM_PATH
- Bundler.with_original_env { Popen.popen(cmd, nil, vars) }
- end
-
def git_timeout
Gitlab.config.gitlab_shell.git_timeout
end
@@ -407,16 +341,8 @@ module Gitlab
def batch_read_key_ids(batch_size: 100, &block)
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- IO.popen("#{gitlab_shell_keys_path} list-key-ids") do |key_id_stream|
- key_id_stream.lazy.each_slice(batch_size) do |lines|
- yield(lines.map { |l| l.chomp.to_i })
- end
- end
- else
- gitlab_authorized_keys.list_key_ids.lazy.each_slice(batch_size) do |key_ids|
- yield(key_ids)
- end
+ gitlab_authorized_keys.list_key_ids.lazy.each_slice(batch_size) do |key_ids|
+ yield(key_ids)
end
end
diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb
index 7c963fcf38a..905e0ec5cc1 100644
--- a/lib/gitlab/slash_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -9,6 +9,7 @@ module Gitlab
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
+ Gitlab::SlashCommands::IssueClose,
Gitlab::SlashCommands::Deploy,
Gitlab::SlashCommands::Run
]
diff --git a/lib/gitlab/slash_commands/issue_close.rb b/lib/gitlab/slash_commands/issue_close.rb
new file mode 100644
index 00000000000..5fcc86e91c4
--- /dev/null
+++ b/lib/gitlab/slash_commands/issue_close.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ class IssueClose < IssueCommand
+ def self.match(text)
+ /\Aissue\s+close\s+#{Issue.reference_prefix}?(?<iid>\d+)/.match(text)
+ end
+
+ def self.help_message
+ "issue close <id>"
+ end
+
+ def self.allowed?(project, user)
+ can?(user, :update_issue, project)
+ end
+
+ def execute(match)
+ issue = find_by_iid(match[:iid])
+
+ return not_found unless issue
+ return presenter(issue).already_closed if issue.closed?
+
+ close_issue(issue: issue)
+
+ presenter(issue).present
+ end
+
+ private
+
+ def close_issue(issue:)
+ Issues::CloseService.new(project, current_user).execute(issue)
+ end
+
+ def presenter(issue)
+ Gitlab::SlashCommands::Presenters::IssueClose.new(issue)
+ end
+
+ def not_found
+ Gitlab::SlashCommands::Presenters::Access.new.not_found
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/issue_base.rb b/lib/gitlab/slash_commands/presenters/issue_base.rb
index b6db103b82b..08cb82274fd 100644
--- a/lib/gitlab/slash_commands/presenters/issue_base.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_base.rb
@@ -40,6 +40,14 @@ module Gitlab
]
end
+ def project_link
+ "[#{project.full_name}](#{project.web_url})"
+ end
+
+ def author_profile_link
+ "[#{author.to_reference}](#{url_for(author)})"
+ end
+
private
attr_reader :resource
diff --git a/lib/gitlab/slash_commands/presenters/issue_close.rb b/lib/gitlab/slash_commands/presenters/issue_close.rb
new file mode 100644
index 00000000000..b3f24f4296a
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/issue_close.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ module Presenters
+ class IssueClose < Presenters::Base
+ include Presenters::IssueBase
+
+ def present
+ if @resource.confidential?
+ ephemeral_response(close_issue)
+ else
+ in_channel_response(close_issue)
+ end
+ end
+
+ def already_closed
+ ephemeral_response(text: "Issue #{@resource.to_reference} is already closed.")
+ end
+
+ private
+
+ def close_issue
+ {
+ attachments: [
+ {
+ title: "#{@resource.title} · #{@resource.to_reference}",
+ title_link: resource_url,
+ author_name: author.name,
+ author_icon: author.avatar_url,
+ fallback: "Closed issue #{@resource.to_reference}: #{@resource.title}",
+ pretext: pretext,
+ color: color(@resource),
+ fields: fields,
+ mrkdwn_in: [
+ :title,
+ :pretext,
+ :fields
+ ]
+ }
+ ]
+ }
+ end
+
+ def pretext
+ "I closed an issue on #{author_profile_link}'s behalf: *#{@resource.to_reference}* in #{project_link}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/issue_new.rb b/lib/gitlab/slash_commands/presenters/issue_new.rb
index ac78745ae70..1424a4ac381 100644
--- a/lib/gitlab/slash_commands/presenters/issue_new.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_new.rb
@@ -36,15 +36,7 @@ module Gitlab
end
def pretext
- "I created an issue on #{author_profile_link}'s behalf: **#{@resource.to_reference}** in #{project_link}"
- end
-
- def project_link
- "[#{project.full_name}](#{project.web_url})"
- end
-
- def author_profile_link
- "[#{author.to_reference}](#{url_for(author)})"
+ "I created an issue on #{author_profile_link}'s behalf: *#{@resource.to_reference}* in #{project_link}"
end
end
end
diff --git a/lib/gitlab/snowplow_tracker.rb b/lib/gitlab/snowplow_tracker.rb
deleted file mode 100644
index 9f12513e09e..00000000000
--- a/lib/gitlab/snowplow_tracker.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-require 'snowplow-tracker'
-
-module Gitlab
- module SnowplowTracker
- NAMESPACE = 'cf'
-
- class << self
- def track_event(category, action, label: nil, property: nil, value: nil, context: nil)
- tracker&.track_struct_event(category, action, label, property, value, context, Time.now.to_i)
- end
-
- private
-
- def tracker
- return unless enabled?
-
- @tracker ||= ::SnowplowTracker::Tracker.new(emitter, subject, NAMESPACE, Gitlab::CurrentSettings.snowplow_site_id)
- end
-
- def subject
- ::SnowplowTracker::Subject.new
- end
-
- def emitter
- ::SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname)
- end
-
- def enabled?
- Gitlab::CurrentSettings.snowplow_enabled?
- end
- end
- end
-end
diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb
new file mode 100644
index 00000000000..ef669b03c87
--- /dev/null
+++ b/lib/gitlab/tracking.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'snowplow-tracker'
+
+module Gitlab
+ module Tracking
+ SNOWPLOW_NAMESPACE = 'gl'
+
+ class << self
+ def enabled?
+ Gitlab::CurrentSettings.snowplow_enabled?
+ end
+
+ def event(category, action, label: nil, property: nil, value: nil, context: nil)
+ return unless enabled?
+
+ snowplow.track_struct_event(category, action, label, property, value, context, Time.now.to_i)
+ end
+
+ def snowplow_options(group)
+ additional_features = Feature.enabled?(:additional_snowplow_tracking, group)
+ {
+ namespace: SNOWPLOW_NAMESPACE,
+ hostname: Gitlab::CurrentSettings.snowplow_collector_hostname,
+ cookie_domain: Gitlab::CurrentSettings.snowplow_cookie_domain,
+ app_id: Gitlab::CurrentSettings.snowplow_site_id,
+ page_tracking_enabled: additional_features,
+ activity_tracking_enabled: additional_features
+ }.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
+ end
+
+ private
+
+ def snowplow
+ @snowplow ||= SnowplowTracker::Tracker.new(
+ SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname),
+ SnowplowTracker::Subject.new,
+ SNOWPLOW_NAMESPACE,
+ Gitlab::CurrentSettings.snowplow_site_id
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/visibility_level_checker.rb b/lib/gitlab/visibility_level_checker.rb
new file mode 100644
index 00000000000..f15f1486a4e
--- /dev/null
+++ b/lib/gitlab/visibility_level_checker.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+# Gitlab::VisibilityLevelChecker verifies that:
+# - Current @project.visibility_level is not restricted
+# - Override visibility param is not restricted
+# - @see https://docs.gitlab.com/ce/api/project_import_export.html#import-a-file
+#
+# @param current_user [User] Current user object to verify visibility level against
+# @param project [Project] Current project that is being created/imported
+# @param project_params [Hash] Supplementary project params (e.g. import
+# params containing visibility override)
+#
+# @example
+# user = User.find(2)
+# project = Project.last
+# project_params = {:import_data=>{:data=>{:override_params=>{"visibility"=>"public"}}}}
+# level_checker = Gitlab::VisibilityLevelChecker.new(user, project, project_params: project_params)
+#
+# project_visibility = level_checker.level_restricted?
+# => #<Gitlab::VisibilityEvaluationResult:0x00007fbe16ee33c0 @restricted=true, @visibility_level=20>
+#
+# if project_visibility.restricted?
+# deny_visibility_level(project, project_visibility.visibility_level)
+# end
+#
+# @return [VisibilityEvaluationResult] Visibility evaluation result. Responds to:
+# #restricted - boolean indicating if level is restricted
+# #visibility_level - integer of restricted visibility level
+#
+module Gitlab
+ class VisibilityLevelChecker
+ def initialize(current_user, project, project_params: {})
+ @current_user = current_user
+ @project = project
+ @project_params = project_params
+ end
+
+ def level_restricted?
+ return VisibilityEvaluationResult.new(true, override_visibility_level_value) if override_visibility_restricted?
+ return VisibilityEvaluationResult.new(true, project.visibility_level) if project_visibility_restricted?
+
+ VisibilityEvaluationResult.new(false, nil)
+ end
+
+ private
+
+ attr_reader :current_user, :project, :project_params
+
+ def override_visibility_restricted?
+ return unless import_data
+ return unless override_visibility_level
+ return if Gitlab::VisibilityLevel.allowed_for?(current_user, override_visibility_level_value)
+
+ true
+ end
+
+ def project_visibility_restricted?
+ return if Gitlab::VisibilityLevel.allowed_for?(current_user, project.visibility_level)
+
+ true
+ end
+
+ def import_data
+ @import_data ||= project_params[:import_data]
+ end
+
+ def override_visibility_level
+ @override_visibility_level ||= import_data.deep_symbolize_keys.dig(:data, :override_params, :visibility)
+ end
+
+ def override_visibility_level_value
+ @override_visibility_level_value ||= Gitlab::VisibilityLevel.level_value(override_visibility_level)
+ end
+ end
+
+ class VisibilityEvaluationResult
+ attr_reader :visibility_level
+
+ def initialize(restricted, visibility_level)
+ @restricted = restricted
+ @visibility_level = visibility_level
+ end
+
+ def restricted?
+ @restricted
+ end
+ end
+end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 3b77fe838ae..29087d26007 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -34,7 +34,8 @@ module Gitlab
GitConfigOptions: [],
GitalyServer: {
address: Gitlab::GitalyClient.address(project.repository_storage),
- token: Gitlab::GitalyClient.token(project.repository_storage)
+ token: Gitlab::GitalyClient.token(project.repository_storage),
+ features: Feature::Gitaly.server_feature_flags
}
}
@@ -250,7 +251,8 @@ module Gitlab
def gitaly_server_hash(repository)
{
address: Gitlab::GitalyClient.address(repository.project.repository_storage),
- token: Gitlab::GitalyClient.token(repository.project.repository_storage)
+ token: Gitlab::GitalyClient.token(repository.project.repository_storage),
+ features: Feature::Gitaly.server_feature_flags
}
end
diff --git a/lib/peek/views/active_record.rb b/lib/peek/views/active_record.rb
index 2d78818630d..a35783c1971 100644
--- a/lib/peek/views/active_record.rb
+++ b/lib/peek/views/active_record.rb
@@ -3,6 +3,24 @@
module Peek
module Views
class ActiveRecord < DetailedView
+ DEFAULT_THRESHOLDS = {
+ calls: 100,
+ duration: 3,
+ individual_call: 1
+ }.freeze
+
+ THRESHOLDS = {
+ production: {
+ calls: 100,
+ duration: 15,
+ individual_call: 5
+ }
+ }.freeze
+
+ def self.thresholds
+ @thresholds ||= THRESHOLDS.fetch(Rails.env.to_sym, DEFAULT_THRESHOLDS)
+ end
+
private
def setup_subscribers
diff --git a/lib/peek/views/detailed_view.rb b/lib/peek/views/detailed_view.rb
index f4ca1cb5075..4f3eddaf11b 100644
--- a/lib/peek/views/detailed_view.rb
+++ b/lib/peek/views/detailed_view.rb
@@ -3,11 +3,16 @@
module Peek
module Views
class DetailedView < View
+ def self.thresholds
+ {}
+ end
+
def results
{
- duration: formatted_duration,
+ duration: format_duration(duration),
calls: calls,
- details: details
+ details: details,
+ warnings: warnings
}
end
@@ -18,30 +23,48 @@ module Peek
private
def duration
- detail_store.map { |entry| entry[:duration] }.sum # rubocop:disable CodeReuse/ActiveRecord
+ detail_store.map { |entry| entry[:duration] }.sum * 1000 # rubocop:disable CodeReuse/ActiveRecord
end
def calls
detail_store.count
end
+ def details
+ call_details
+ .sort { |a, b| b[:duration] <=> a[:duration] }
+ .map(&method(:format_call_details))
+ end
+
+ def warnings
+ [
+ warning_for(calls, self.class.thresholds[:calls], label: "#{key} calls"),
+ warning_for(duration, self.class.thresholds[:duration], label: "#{key} duration")
+ ].flatten.compact
+ end
+
def call_details
detail_store
end
def format_call_details(call)
- call.merge(duration: (call[:duration] * 1000).round(3))
- end
+ duration = (call[:duration] * 1000).round(3)
- def details
- call_details
- .sort { |a, b| b[:duration] <=> a[:duration] }
- .map(&method(:format_call_details))
+ call.merge(duration: duration,
+ warnings: warning_for(duration, self.class.thresholds[:individual_call]))
end
- def formatted_duration
- ms = duration * 1000
+ def warning_for(actual, threshold, label: nil)
+ if threshold && actual > threshold
+ prefix = "#{label}: " if label
+
+ ["#{prefix}#{actual} over #{threshold}"]
+ else
+ []
+ end
+ end
+ def format_duration(ms)
if ms >= 1000
"%.2fms" % ms
else
diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb
index 6ad6ddfd89d..f669feae254 100644
--- a/lib/peek/views/gitaly.rb
+++ b/lib/peek/views/gitaly.rb
@@ -3,6 +3,24 @@
module Peek
module Views
class Gitaly < DetailedView
+ DEFAULT_THRESHOLDS = {
+ calls: 30,
+ duration: 1,
+ individual_call: 0.5
+ }.freeze
+
+ THRESHOLDS = {
+ production: {
+ calls: 30,
+ duration: 1,
+ individual_call: 0.5
+ }
+ }.freeze
+
+ def self.thresholds
+ @thresholds ||= THRESHOLDS.fetch(Rails.env.to_sym, DEFAULT_THRESHOLDS)
+ end
+
private
def duration
diff --git a/lib/peek/views/rugged.rb b/lib/peek/views/rugged.rb
index 18b3f422852..3ed54a010f8 100644
--- a/lib/peek/views/rugged.rb
+++ b/lib/peek/views/rugged.rb
@@ -12,7 +12,7 @@ module Peek
private
def duration
- ::Gitlab::RuggedInstrumentation.query_time
+ ::Gitlab::RuggedInstrumentation.query_time_ms
end
def calls
diff --git a/lib/system_check/app/authorized_keys_permission_check.rb b/lib/system_check/app/authorized_keys_permission_check.rb
new file mode 100644
index 00000000000..1246a6875a3
--- /dev/null
+++ b/lib/system_check/app/authorized_keys_permission_check.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module SystemCheck
+ module App
+ class AuthorizedKeysPermissionCheck < SystemCheck::BaseCheck
+ set_name 'Is authorized keys file accessible?'
+ set_skip_reason 'skipped (authorized keys not enabled)'
+
+ def skip?
+ !authorized_keys_enabled?
+ end
+
+ def check?
+ authorized_keys.accessible?
+ end
+
+ def repair!
+ authorized_keys.create
+ end
+
+ def show_error
+ try_fixing_it([
+ "sudo chmod 700 #{File.dirname(authorized_keys.file)}",
+ "touch #{authorized_keys.file}",
+ "sudo chmod 600 #{authorized_keys.file}"
+ ])
+ fix_and_rerun
+ end
+
+ private
+
+ def authorized_keys_enabled?
+ Gitlab::CurrentSettings.current_application_settings.authorized_keys_enabled
+ end
+
+ def authorized_keys
+ @authorized_keys ||= Gitlab::AuthorizedKeys.new
+ end
+ end
+ end
+end
diff --git a/lib/system_check/rake_task/app_task.rb b/lib/system_check/rake_task/app_task.rb
index cc32feb8604..e98cee510ff 100644
--- a/lib/system_check/rake_task/app_task.rb
+++ b/lib/system_check/rake_task/app_task.rb
@@ -30,7 +30,8 @@ module SystemCheck
SystemCheck::App::RubyVersionCheck,
SystemCheck::App::GitVersionCheck,
SystemCheck::App::GitUserDefaultSSHConfigCheck,
- SystemCheck::App::ActiveUsersCheck
+ SystemCheck::App::ActiveUsersCheck,
+ SystemCheck::App::AuthorizedKeysPermissionCheck
]
end
end
diff --git a/lib/tasks/gitlab/uploads/sanitize.rake b/lib/tasks/gitlab/uploads/sanitize.rake
index 12cf5302555..4f23a0a5d82 100644
--- a/lib/tasks/gitlab/uploads/sanitize.rake
+++ b/lib/tasks/gitlab/uploads/sanitize.rake
@@ -2,7 +2,7 @@ namespace :gitlab do
namespace :uploads do
namespace :sanitize do
desc 'GitLab | Uploads | Remove EXIF from images.'
- task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time] => :environment do |task, args|
+ task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time, :uploader, :since] => :environment do |task, args|
args.with_defaults(dry_run: 'true')
args.with_defaults(sleep_time: 0.3)
@@ -11,7 +11,9 @@ namespace :gitlab do
sanitizer = Gitlab::Sanitizers::Exif.new(logger: logger)
sanitizer.batch_clean(start_id: args.start_id, stop_id: args.stop_id,
dry_run: args.dry_run != 'false',
- sleep_time: args.sleep_time.to_f)
+ sleep_time: args.sleep_time.to_f,
+ uploader: args.uploader,
+ since: args.since)
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e44c18979cd..705c2921298 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -309,6 +309,9 @@ msgstr ""
msgid "(external source)"
msgstr ""
+msgid "(removed)"
+msgstr ""
+
msgid "+ %{amount} more"
msgstr ""
@@ -1101,9 +1104,6 @@ msgstr ""
msgid "An error occurred while parsing recent searches"
msgstr ""
-msgid "An error occurred while rendering KaTeX"
-msgstr ""
-
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
@@ -1513,6 +1513,18 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
+msgid "Autocomplete"
+msgstr ""
+
+msgid "Autocomplete description"
+msgstr ""
+
+msgid "Autocomplete hint"
+msgstr ""
+
+msgid "Autocomplete usage hint"
+msgstr ""
+
msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
msgstr ""
@@ -2971,6 +2983,9 @@ msgstr ""
msgid "ComboSearch is not defined"
msgstr ""
+msgid "Command"
+msgstr ""
+
msgid "Command line instructions"
msgstr ""
@@ -3608,9 +3623,15 @@ msgstr ""
msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Customize icon"
+msgstr ""
+
msgid "Customize language and region related settings."
msgstr ""
+msgid "Customize name"
+msgstr ""
+
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3961,6 +3982,9 @@ msgstr ""
msgid "Description:"
msgstr ""
+msgid "Descriptive label"
+msgstr ""
+
msgid "Deselect all"
msgstr ""
@@ -4072,6 +4096,9 @@ msgstr ""
msgid "Dismiss Cycle Analytics introduction box"
msgstr ""
+msgid "Display name"
+msgstr ""
+
msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
msgstr ""
@@ -4117,6 +4144,9 @@ msgstr ""
msgid "Download export"
msgstr ""
+msgid "Download image"
+msgstr ""
+
msgid "Download source code"
msgstr ""
@@ -4327,7 +4357,7 @@ msgstr ""
msgid "Enable or disable version check and usage ping."
msgstr ""
-msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgid "Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}"
msgstr ""
msgid "Enable shared Runners"
@@ -5697,7 +5727,10 @@ msgstr ""
msgid "Help page text and support page url."
msgstr ""
-msgid "Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}"
+msgid "Helps prevent bots from brute-force attacks."
+msgstr ""
+
+msgid "Helps prevent bots from creating accounts."
msgstr ""
msgid "Hide archived projects"
@@ -6107,6 +6140,9 @@ msgstr ""
msgid "Invalid Login or password"
msgstr ""
+msgid "Invalid URL"
+msgstr ""
+
msgid "Invalid date"
msgstr ""
@@ -6831,6 +6867,36 @@ msgstr ""
msgid "Marks this issue as a duplicate of %{duplicate_reference}."
msgstr ""
+msgid "MattermostService|Add to Mattermost"
+msgstr ""
+
+msgid "MattermostService|Command trigger word"
+msgstr ""
+
+msgid "MattermostService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "MattermostService|Request URL"
+msgstr ""
+
+msgid "MattermostService|Request method"
+msgstr ""
+
+msgid "MattermostService|Response icon"
+msgstr ""
+
+msgid "MattermostService|Response username"
+msgstr ""
+
+msgid "MattermostService|See list of available commands in Mattermost after setting up this service, by entering"
+msgstr ""
+
+msgid "MattermostService|Suggestions:"
+msgstr ""
+
+msgid "MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost."
+msgstr ""
+
msgid "Max access level"
msgstr ""
@@ -7008,6 +7074,9 @@ msgstr ""
msgid "Messages"
msgstr ""
+msgid "Method"
+msgstr ""
+
msgid "Metrics"
msgstr ""
@@ -7927,6 +7996,9 @@ msgstr ""
msgid "Perform advanced options such as changing path, transferring, or removing the group."
msgstr ""
+msgid "Perform common operations on GitLab project"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -8884,6 +8956,39 @@ msgstr ""
msgid "ProjectSelect|Search for project"
msgstr ""
+msgid "ProjectService|%{service_title}: status off"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status on"
+msgstr ""
+
+msgid "ProjectService|Integrations"
+msgstr ""
+
+msgid "ProjectService|Last edit"
+msgstr ""
+
+msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
+msgstr ""
+
+msgid "ProjectService|Project services"
+msgstr ""
+
+msgid "ProjectService|Project services allow you to integrate GitLab with other applications"
+msgstr ""
+
+msgid "ProjectService|Service"
+msgstr ""
+
+msgid "ProjectService|Services"
+msgstr ""
+
+msgid "ProjectService|Settings"
+msgstr ""
+
+msgid "ProjectService|To set up this service:"
+msgstr ""
+
msgid "ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed"
msgstr ""
@@ -10411,6 +10516,21 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "SlackService|2. Paste the <strong>Token</strong> into the field below"
+msgstr ""
+
+msgid "SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!"
+msgstr ""
+
+msgid "SlackService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "SlackService|See list of available commands in Slack after setting up this service, by entering"
+msgstr ""
+
+msgid "SlackService|This service allows users to perform common operations on this project by entering slash commands in Slack."
+msgstr ""
+
msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
msgstr ""
@@ -10984,6 +11104,9 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestions:"
+msgstr ""
+
msgid "Sunday"
msgstr ""
@@ -12040,6 +12163,9 @@ msgstr ""
msgid "To see all the user's personal access tokens you must impersonate them first."
msgstr ""
+msgid "To set up this service:"
+msgstr ""
+
msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
msgstr ""
@@ -12112,9 +12238,6 @@ msgstr ""
msgid "Toggles :%{name}: emoji award."
msgstr ""
-msgid "Token"
-msgstr ""
-
msgid "Tomorrow"
msgstr ""
@@ -13676,6 +13799,12 @@ msgstr ""
msgid "manual"
msgstr ""
+msgid "math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead."
+msgstr ""
+
+msgid "math|There was an error rendering this math block"
+msgstr ""
+
msgid "merge request"
msgid_plural "merge requests"
msgstr[0] ""
diff --git a/package.json b/package.json
index 8fa34cdea28..482f5cc7349 100644
--- a/package.json
+++ b/package.json
@@ -36,10 +36,9 @@
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.5.5",
- "@gitlab/csslab": "^1.9.0",
- "@gitlab/svgs": "^1.70.0",
- "@gitlab/ui": "5.19.0",
- "@gitlab/visual-review-tools": "^1.0.0",
+ "@gitlab/svgs": "^1.71.0",
+ "@gitlab/ui": "5.20.1",
+ "@gitlab/visual-review-tools": "1.0.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
@@ -97,7 +96,7 @@
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
"marked": "^0.3.12",
- "mermaid": "^8.2.3",
+ "mermaid": "^8.2.4",
"monaco-editor": "^0.15.6",
"monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
diff --git a/qa/README.md b/qa/README.md
index 16e5e730a6b..dede3cd2473 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -105,6 +105,17 @@ If you need to authenticate as a different user, you can provide the
GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec bin/qa Test::Instance::All https://gitlab.example.com
```
+Some QA tests require logging in as an admin user. By default, the QA
+tests will use the the same `root` user seeded by the GDK.
+
+If you need to authenticate with different admin credentials, you can
+provide the `GITLAB_ADMIN_USERNAME` and `GITLAB_ADMIN_PASSWORD`
+environment variables:
+
+```
+GITLAB_ADMIN_USERNAME=admin GITLAB_ADMIN_PASSWORD=myadminpassword GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec bin/qa Test::Instance::All https://gitlab.example.com
+```
+
If your user doesn't have permission to default sandbox group
`gitlab-qa-sandbox`, you could also use another sandbox group by giving
`GITLAB_SANDBOX_NAME`:
diff --git a/qa/qa.rb b/qa/qa.rb
index 22c8ef645d2..8b38011486b 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -73,6 +73,7 @@ module QA
end
module Repository
+ autoload :Commit, 'qa/resource/repository/commit'
autoload :Push, 'qa/resource/repository/push'
autoload :ProjectPush, 'qa/resource/repository/project_push'
autoload :WikiPush, 'qa/resource/repository/wiki_push'
@@ -160,6 +161,10 @@ module QA
module Group
autoload :New, 'qa/page/group/new'
autoload :Show, 'qa/page/group/show'
+
+ module Settings
+ autoload :General, 'qa/page/group/settings/general'
+ end
end
module File
@@ -304,8 +309,10 @@ module QA
autoload :Repository, 'qa/page/admin/settings/repository'
autoload :General, 'qa/page/admin/settings/general'
autoload :MetricsAndProfiling, 'qa/page/admin/settings/metrics_and_profiling'
+ autoload :Network, 'qa/page/admin/settings/network'
module Component
+ autoload :IpLimits, 'qa/page/admin/settings/component/ip_limits'
autoload :RepositoryStorage, 'qa/page/admin/settings/component/repository_storage'
autoload :AccountAndLimit, 'qa/page/admin/settings/component/account_and_limit'
autoload :PerformanceBar, 'qa/page/admin/settings/component/performance_bar'
@@ -340,6 +347,10 @@ module QA
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
end
+
+ module WebIDE
+ autoload :Alert, 'qa/page/component/web_ide/alert'
+ end
end
end
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index 61ec9854726..7c214da8486 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -49,6 +49,14 @@ module QA
end
end
+ def go_to_network_settings
+ hover_settings do
+ within_submenu do
+ click_element :admin_settings_network_item
+ end
+ end
+ end
+
private
def hover_settings
@@ -75,3 +83,5 @@ module QA
end
end
end
+
+QA::Page::Admin::Menu.prepend_if_ee('QA::EE::Page::Admin::Menu')
diff --git a/qa/qa/page/admin/settings/component/ip_limits.rb b/qa/qa/page/admin/settings/component/ip_limits.rb
new file mode 100644
index 00000000000..9db2ae8ba58
--- /dev/null
+++ b/qa/qa/page/admin/settings/component/ip_limits.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Admin
+ module Settings
+ module Component
+ class IpLimits < Page::Base
+ view 'app/views/admin/application_settings/_ip_limits.html.haml' do
+ element :throttle_unauthenticated_checkbox
+ element :throttle_authenticated_api_checkbox
+ element :throttle_authenticated_web_checkbox
+ element :save_changes_button
+ end
+
+ def enable_throttles
+ check_element :throttle_unauthenticated_checkbox
+ check_element :throttle_authenticated_api_checkbox
+ check_element :throttle_authenticated_web_checkbox
+ end
+
+ def save_settings
+ click_element :save_changes_button
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/admin/settings/network.rb b/qa/qa/page/admin/settings/network.rb
new file mode 100644
index 00000000000..fdb8fcda281
--- /dev/null
+++ b/qa/qa/page/admin/settings/network.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Admin
+ module Settings
+ class Network < Page::Base
+ include QA::Page::Settings::Common
+
+ view 'app/views/admin/application_settings/network.html.haml' do
+ element :ip_limits_section
+ end
+
+ def expand_ip_limits(&block)
+ expand_section(:ip_limits_section) do
+ Component::IpLimits.perform(&block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/web_ide/alert.rb b/qa/qa/page/component/web_ide/alert.rb
new file mode 100644
index 00000000000..0f0623d5ebf
--- /dev/null
+++ b/qa/qa/page/component/web_ide/alert.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module WebIDE
+ module Alert
+ def self.prepended(page)
+ page.module_eval do
+ view 'app/assets/javascripts/ide/components/error_message.vue' do
+ element :flash_alert
+ end
+ end
+ end
+
+ def has_no_alert?(message = nil)
+ return has_no_element?(:flash_alert) if message.nil?
+
+ within_element(:flash_alert) do
+ has_no_text?(message)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index 0c23d7cffbb..378ac793f7b 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -29,3 +29,5 @@ module QA
end
end
end
+
+QA::Page::Dashboard::Projects.prepend_if_ee('QA::EE::Page::Dashboard::Projects')
diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb
index 92f9181f99d..f5f44909f25 100644
--- a/qa/qa/page/file/show.rb
+++ b/qa/qa/page/file/show.rb
@@ -32,3 +32,5 @@ module QA
end
end
end
+
+QA::Page::File::Show.prepend_if_ee('QA::EE::Page::File::Show')
diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb
new file mode 100644
index 00000000000..07b421f154a
--- /dev/null
+++ b/qa/qa/page/group/settings/general.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Group
+ module Settings
+ class General < QA::Page::Base
+ view 'app/views/groups/edit.html.haml' do
+ element :permission_lfs_2fa_section
+ end
+ view 'app/views/groups/settings/_permissions.html.haml' do
+ element :save_permissions_changes_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 6a415b56e50..72f8e1c3ef0 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -187,3 +187,5 @@ module QA
end
end
end
+
+QA::Page::MergeRequest::Show.prepend_if_ee('QA::EE::Page::MergeRequest::Show')
diff --git a/qa/qa/page/profile/menu.rb b/qa/qa/page/profile/menu.rb
index 2d503499e13..99a795a23ef 100644
--- a/qa/qa/page/profile/menu.rb
+++ b/qa/qa/page/profile/menu.rb
@@ -34,3 +34,5 @@ module QA
end
end
end
+
+QA::Page::Profile::Menu.prepend_if_ee('QA::EE::Page::Profile::Menu')
diff --git a/qa/qa/page/project/commit/show.rb b/qa/qa/page/project/commit/show.rb
index 9770b8a657c..ba09dd1b92a 100644
--- a/qa/qa/page/project/commit/show.rb
+++ b/qa/qa/page/project/commit/show.rb
@@ -9,6 +9,7 @@ module QA
element :options_button
element :email_patches
element :plain_diff
+ element :commit_sha_content
end
def select_email_patches
@@ -20,6 +21,10 @@ module QA
click_element :options_button
click_element :plain_diff
end
+
+ def commit_sha
+ find_element(:commit_sha_content).text
+ end
end
end
end
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index c4383951ec4..f74366f6967 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -9,11 +9,21 @@ module QA
element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
end
+ view 'app/views/shared/issuable/_nav.html.haml' do
+ element :closed_issues_link
+ end
+
def click_issue_link(title)
click_link(title)
end
+
+ def click_closed_issues_link
+ click_element :closed_issues_link
+ end
end
end
end
end
end
+
+QA::Page::Project::Issue::Index.prepend_if_ee('QA::EE::Page::Project::Issue::Index')
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 45dad9bc0ae..52929ece9ed 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -37,6 +37,10 @@ module QA
element :dropdown_menu_labels
end
+ view 'app/views/shared/issuable/_close_reopen_button.html.haml' do
+ element :reopen_issue_button
+ end
+
# Adds a comment to an issue
# attachment option should be an absolute path
def comment(text, attachment: nil, filter: :all_activities)
@@ -108,3 +112,5 @@ module QA
end
end
end
+
+QA::Page::Project::Issue::Show.prepend_if_ee('QA::EE::Page::Project::Issue::Show')
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 838d59b59cb..a9226927741 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -39,3 +39,5 @@ module QA
end
end
end
+
+QA::Page::Project::Menu.prepend_if_ee('QA::EE::Page::Project::SubMenus::SecurityCompliance')
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 64aab9be056..d0e8011d82d 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -73,3 +73,5 @@ module QA
end
end
end
+
+QA::Page::Project::New.prepend_if_ee('QA::EE::Page::Project::New')
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index eb30e0ea02a..fa276f15b8a 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -53,3 +53,5 @@ module QA
end
end
end
+
+QA::Page::Project::Operations::Kubernetes::Show.prepend_if_ee('QA::EE::Page::Project::Operations::Kubernetes::Show')
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 284d0957eb8..3dca47a57e9 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -60,3 +60,5 @@ module QA::Page
end
end
end
+
+QA::Page::Project::Pipeline::Show.prepend_if_ee('QA::EE::Page::Project::Pipeline::Show')
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index dbbe62e3b1d..a196fc0123a 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -41,3 +41,5 @@ module QA
end
end
end
+
+QA::Page::Project::Settings::Main.prepend_if_ee('QA::EE::Page::Project::Settings::Main')
diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb
index 831166f6373..e3afaceda80 100644
--- a/qa/qa/page/project/settings/mirroring_repositories.rb
+++ b/qa/qa/page/project/settings/mirroring_repositories.rb
@@ -89,3 +89,5 @@ module QA
end
end
end
+
+QA::Page::Project::Settings::MirroringRepositories.prepend_if_ee('QA::EE::Page::Project::Settings::MirroringRepositories')
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index 903b0979614..1e707f1d315 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -73,3 +73,5 @@ module QA
end
end
end
+
+QA::Page::Project::Settings::ProtectedBranches.prepend_if_ee('QA::EE::Page::Project::Settings::ProtectedBranches')
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 9fd668f812b..850a96d87b0 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -131,3 +131,5 @@ module QA
end
end
end
+
+QA::Page::Project.prepend_if_ee('QA::EE::Page::Project::Show')
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 37bca97fec7..4d26cadcdfe 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -5,6 +5,7 @@ module QA
module Project
module WebIDE
class Edit < Page::Base
+ prepend Page::Component::WebIDE::Alert
include Page::Component::DropdownFilter
view 'app/assets/javascripts/ide/components/activity_bar.vue' do
@@ -34,6 +35,10 @@ module QA
element :dropdown_filter_input
end
+ view 'app/assets/javascripts/ide/components/commit_sidebar/actions.vue' do
+ element :commit_to_current_branch_radio
+ end
+
view 'app/assets/javascripts/ide/components/commit_sidebar/form.vue' do
element :begin_commit_button
element :commit_button
@@ -104,7 +109,7 @@ module QA
# animation is still in process even when the buttons have the
# expected visibility.
commit_success_msg_shown = retry_until do
- uncheck_element :start_new_mr_checkbox
+ click_element :commit_to_current_branch_radio
click_element :commit_button
wait(reload: false) do
@@ -119,3 +124,5 @@ module QA
end
end
end
+
+QA::Page::Project::WebIDE::Edit.prepend_if_ee('QA::EE::Page::Component::WebIDE::WebTerminalPanel')
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 93a82094776..4a29a14c5c2 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -15,6 +15,7 @@ module QA
attribute :add_name_uuid
attribute :description
attribute :standalone
+ attribute :runners_token
attribute :group do
Group.fabricate!
diff --git a/qa/qa/resource/repository/commit.rb b/qa/qa/resource/repository/commit.rb
new file mode 100644
index 00000000000..61c2ad6bfc0
--- /dev/null
+++ b/qa/qa/resource/repository/commit.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ module Repository
+ class Commit < Base
+ attr_accessor :author_email,
+ :author_name,
+ :branch,
+ :commit_message,
+ :file_path,
+ :sha
+
+ attribute :project do
+ Project.fabricate! do |resource|
+ resource.name = 'project-with-commit'
+ end
+ end
+
+ def initialize
+ @commit_message = 'QA Test - Commit message'
+ end
+
+ def files=(files)
+ if !files.is_a?(Array) ||
+ files.empty? ||
+ files.any? { |file| !file.has_key?(:file_path) || !file.has_key?(:content) }
+ raise ArgumentError, "Please provide an array of hashes e.g.: [{file_path: 'file1', content: 'foo'}]"
+ end
+
+ @files = files
+ end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+ end
+
+ def api_get_path
+ "#{api_post_path}/#{@sha}"
+ end
+
+ def api_post_path
+ "/projects/#{CGI.escape(project.path_with_namespace)}/repository/commits"
+ end
+
+ def api_post_body
+ {
+ branch: @branch || "master",
+ author_email: @author_email || Runtime::User.default_email,
+ author_name: @author_name || Runtime::User.username,
+ commit_message: commit_message,
+ actions: actions
+ }
+ end
+
+ def actions
+ @files.map do |file|
+ file.merge({ action: "create" })
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index 3344ad3360a..9c2e138bade 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -6,9 +6,10 @@ module QA
module Resource
class Runner < Base
attr_writer :name, :tags, :image
+ attr_accessor :config
attribute :project do
- Project.fabricate! do |resource|
+ Project.fabricate_via_api! do |resource|
resource.name = 'project-with-ci-cd'
resource.description = 'Project with CI/CD Pipelines'
end
@@ -26,24 +27,27 @@ module QA
@image || 'gitlab/gitlab-runner:alpine'
end
- def fabricate!
- project.visit!
-
- Page::Project::Menu.perform(&:go_to_ci_cd_settings)
-
+ def fabricate_via_api!
Service::Runner.new(name).tap do |runner|
- Page::Project::Settings::CICD.perform do |settings|
- settings.expand_runners_settings do |runners|
- runner.pull
- runner.token = runners.registration_token
- runner.address = runners.coordinator_address
- runner.tags = tags
- runner.image = image
- runner.register!
- end
- end
+ runner.pull
+ runner.token = project.runners_token
+ runner.address = Runtime::Scenario.gitlab_address
+ runner.tags = tags
+ runner.image = image
+ runner.config = config if config
+ runner.run_untagged = true
+ runner.register!
end
end
+
+ def api_get_path
+ end
+
+ def api_post_path
+ end
+
+ def api_post_body
+ end
end
end
end
diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb
index e2b1c4c0831..6eff0e87ddc 100644
--- a/qa/qa/resource/sandbox.rb
+++ b/qa/qa/resource/sandbox.rb
@@ -7,7 +7,7 @@ module QA
# creating it if it doesn't yet exist.
#
class Sandbox < Base
- attr_reader :path
+ attr_accessor :path
attribute :id
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index 663be27a849..25d8f3c0fbb 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -8,11 +8,12 @@ module QA
class Client
attr_reader :address, :user
- def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true, user: nil)
+ def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true, user: nil, ip_limits: false)
@address = address
@personal_access_token = personal_access_token
@is_new_session = is_new_session
@user = user
+ enable_ip_limits if ip_limits
end
def personal_access_token
@@ -26,6 +27,24 @@ module QA
private
+ def enable_ip_limits
+ Page::Main::Menu.perform(&:sign_out) if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
+
+ Runtime::Browser.visit(@address, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_admin_credentials)
+ Page::Main::Menu.perform(&:click_admin_area)
+ Page::Admin::Menu.perform(&:go_to_network_settings)
+
+ Page::Admin::Settings::Network.perform do |setting|
+ setting.expand_ip_limits do |page|
+ page.enable_throttles
+ page.save_settings
+ end
+ end
+
+ Page::Main::Menu.perform(&:sign_out)
+ end
+
def create_personal_access_token
Page::Main::Menu.perform(&:sign_out) if @is_new_session && Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index b184eeb1701..594e5712ab2 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -233,3 +233,5 @@ module QA
end
end
end
+
+QA::Runtime::Env.extend_if_ee('QA::EE::Runtime::Env')
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index 632a0f5f2a9..99497cbe0ad 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -56,3 +56,5 @@ module QA
end
end
end
+
+QA::Scenario::Test::Sanity::Selectors.prepend_if_ee('QA::EE::Scenario::Test::Sanity::Selectors')
diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb
index 03b234f7a56..6fc5984b12a 100644
--- a/qa/qa/service/runner.rb
+++ b/qa/qa/service/runner.rb
@@ -7,13 +7,25 @@ module QA
class Runner
include Service::Shellout
- attr_accessor :token, :address, :tags, :image
+ attr_accessor :token, :address, :tags, :image, :run_untagged
+ attr_writer :config
def initialize(name)
@image = 'gitlab/gitlab-runner:alpine'
@name = name || "qa-runner-#{SecureRandom.hex(4)}"
@network = Runtime::Scenario.attributes[:network] || 'test'
@tags = %w[qa test]
+ @run_untagged = false
+ end
+
+ def config
+ @config ||= <<~END
+ concurrent = 1
+ check_interval = 0
+
+ [session_server]
+ session_timeout = 1800
+ END
end
def network
@@ -32,19 +44,30 @@ module QA
shell <<~CMD.tr("\n", ' ')
docker run -d --rm --entrypoint=/bin/sh
--network #{network} --name #{@name}
+ -p 8093:8093
-e CI_SERVER_URL=#{@address}
-e REGISTER_NON_INTERACTIVE=true
-e REGISTRATION_TOKEN=#{@token}
-e RUNNER_EXECUTOR=shell
-e RUNNER_TAG_LIST=#{@tags.join(',')}
-e RUNNER_NAME=#{@name}
- #{@image} -c 'gitlab-runner register && gitlab-runner run'
+ #{@image} -c "#{register_command}"
CMD
end
def remove!
shell "docker rm -f #{@name}"
end
+
+ private
+
+ def register_command
+ <<~CMD
+ printf '#{config.chomp.gsub(/\n/, "\\n").gsub('"', '\"')}' > /etc/gitlab-runner/config.toml &&
+ gitlab-runner register --run-untagged=#{@run_untagged} &&
+ gitlab-runner run
+ CMD
+ end
end
end
end
diff --git a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
new file mode 100644
index 00000000000..44c5e0b4196
--- /dev/null
+++ b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Manage with IP rate limits', :requires_admin do
+ describe 'Users API' do
+ before(:context) do
+ @api_client = Runtime::API::Client.new(:gitlab, ip_limits: true)
+ end
+
+ let(:request) { Runtime::API::Request.new(@api_client, '/users') }
+
+ it 'GET /users' do
+ 5.times do
+ get request.url
+ expect_status(200)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb
new file mode 100644
index 00000000000..7b6a3579af0
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan' do
+ describe 'Close issue' do
+ let(:issue_title) { 'issue title' }
+ let(:commit_message) { 'Closes' }
+
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ issue = Resource::Issue.fabricate_via_api! do |issue|
+ issue.title = issue_title
+ end
+
+ @project = issue.project
+ @issue_id = issue.api_response[:iid]
+
+ # Initial commit should be pushed because
+ # the very first commit to the project doesn't close the issue
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/38965
+ push_commit('Initial commit')
+ end
+
+ it 'user closes an issue by pushing commit' do
+ push_commit("#{commit_message} ##{@issue_id}", false)
+
+ @project.visit!
+ Page::Project::Show.perform do |show|
+ show.click_commit(commit_message)
+ end
+ commit_sha = Page::Project::Commit::Show.perform(&:commit_sha)
+
+ Page::Project::Menu.perform(&:click_issues)
+ Page::Project::Issue::Index.perform do |index|
+ index.click_closed_issues_link
+ index.click_issue_link(issue_title)
+ end
+
+ Page::Project::Issue::Show.perform do |show|
+ expect(show).to have_element(:reopen_issue_button)
+ expect(show).to have_content("closed via commit #{commit_sha}")
+ end
+ end
+
+ def push_commit(commit_message, new_branch = true)
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.commit_message = commit_message
+ push.new_branch = new_branch
+ push.file_content = commit_message
+ push.project = @project
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index 33744236dd4..900ddcb7f59 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -15,11 +15,11 @@ module QA
Resource::Runner.fabricate! do |runner|
runner.name = executor
- end
+ end.project.visit!
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
Page::Project::Settings::CICD.perform do |settings|
sleep 5 # Runner should register within 5 seconds
- settings.refresh
settings.expand_runners_settings do |page|
expect(page).to have_content(executor)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index f6411d8c5ad..141166f6971 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -17,7 +17,7 @@ module QA
@repository_location = @project.repository_ssh_location
- Resource::Runner.fabricate_via_browser_ui! do |resource|
+ Resource::Runner.fabricate_via_api! do |resource|
resource.project = @project
resource.name = @runner_name
resource.tags = %w[qa docker]
diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb
index e0e1b2d6c7d..6f007e667f2 100644
--- a/rubocop/cop/inject_enterprise_edition_module.rb
+++ b/rubocop/cop/inject_enterprise_edition_module.rb
@@ -24,7 +24,7 @@ module RuboCop
# We use `match?` here instead of RuboCop's AST matching, as this makes
# it far easier to handle nested constants such as `EE::Foo::Bar::Baz`.
- line.match?(/(\s|\()('|")?(::)?EE::/)
+ line.match?(/(\s|\()('|")?(::)?(QA::)?EE::/)
end
def on_send(node)
diff --git a/rubocop/cop/rspec/be_success_matcher.rb b/rubocop/cop/rspec/be_success_matcher.rb
new file mode 100644
index 00000000000..dce9604b3d7
--- /dev/null
+++ b/rubocop/cop/rspec/be_success_matcher.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module RSpec
+ # This cop checks for `be_success` usage in controller specs
+ #
+ # @example
+ #
+ # # bad
+ # it "responds with success" do
+ # expect(response).to be_success
+ # end
+ #
+ # it { is_expected.to be_success }
+ #
+ # # good
+ # it "responds with success" do
+ # expect(response).to be_successful
+ # end
+ #
+ # it { is_expected.to be_successful }
+ #
+ class BeSuccessMatcher < RuboCop::Cop::Cop
+ MESSAGE = 'Do not use deprecated `success?` method, use `successful?` instead.'
+
+ def_node_search :expect_to_be_success?, <<~PATTERN
+ (send (send nil? :expect (send nil? ...)) {:to :not_to :to_not} (send nil? :be_success))
+ PATTERN
+
+ def_node_search :is_expected_to_be_success?, <<~PATTERN
+ (send (send nil? :is_expected) {:to :not_to :to_not} (send nil? :be_success))
+ PATTERN
+
+ def be_success_usage?(node)
+ expect_to_be_success?(node) || is_expected_to_be_success?(node)
+ end
+
+ def on_send(node)
+ return unless be_success_usage?(node)
+
+ add_offense(node, location: :expression, message: MESSAGE)
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.insert_after(node.loc.expression, 'ful')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index d1328c4eb38..c342df6d6c9 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -31,6 +31,7 @@ require_relative 'cop/migration/timestamps'
require_relative 'cop/migration/update_column_in_batches'
require_relative 'cop/migration/update_large_table'
require_relative 'cop/project_path_helper'
+require_relative 'cop/rspec/be_success_matcher'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/factories_in_migration_specs'
require_relative 'cop/rspec/top_level_describe_path'
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index 418ca6f3210..1e8a8145b35 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -30,6 +30,21 @@ describe Boards::ListsController do
expect(json_response.length).to eq 3
end
+ it 'avoids n+1 queries when serializing lists' do
+ list_1 = create(:list, board: board)
+ list_1.update_preferences_for(user, { collapsed: true })
+
+ control_count = ActiveRecord::QueryRecorder.new { read_board_list user: user, board: board }.count
+
+ list_2 = create(:list, board: board)
+ list_2.update_preferences_for(user, { collapsed: true })
+
+ list_3 = create(:list, board: board)
+ list_3.update_preferences_for(user, { collapsed: true })
+
+ expect { read_board_list user: user, board: board }.not_to exceed_query_limit(control_count)
+ end
+
context 'with unauthorized user' do
let(:unauth_user) { create(:user) }
@@ -154,6 +169,22 @@ describe Boards::ListsController do
end
end
+ context 'with collapsed preference' do
+ it 'saves collapsed preference for user' do
+ save_setting user: user, board: board, list: planning, setting: { collapsed: true }
+
+ expect(planning.preferences_for(user).collapsed).to eq(true)
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'saves not collapsed preference for user' do
+ save_setting user: user, board: board, list: planning, setting: { collapsed: false }
+
+ expect(planning.preferences_for(user).collapsed).to eq(false)
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
def move(user:, board:, list:, position:)
sign_in(user)
@@ -166,6 +197,19 @@ describe Boards::ListsController do
patch :update, params: params, as: :json
end
+
+ def save_setting(user:, board:, list:, setting: {})
+ sign_in(user)
+
+ params = { namespace_id: project.namespace.to_param,
+ project_id: project,
+ board_id: board.to_param,
+ id: list.to_param,
+ list: setting,
+ format: :json }
+
+ patch :update, params: params, as: :json
+ end
end
describe 'DELETE destroy' do
diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb
index 91f9e2c7832..14b0cf959b3 100644
--- a/spec/controllers/groups/runners_controller_spec.rb
+++ b/spec/controllers/groups/runners_controller_spec.rb
@@ -3,73 +3,202 @@
require 'spec_helper'
describe Groups::RunnersController do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
-
- let(:params) do
- {
- group_id: group,
- id: runner
- }
- end
+ let(:params) { { group_id: group, id: runner } }
before do
sign_in(user)
- group.add_maintainer(user)
+ end
+
+ describe '#show' do
+ context 'when user is owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'renders show with 200 status code' do
+ get :show, params: { group_id: group, id: runner }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to render_template(:show)
+ end
+ end
+
+ context 'when user is not owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'renders a 404' do
+ get :show, params: { group_id: group, id: runner }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ describe '#edit' do
+ context 'when user is owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'renders show with 200 status code' do
+ get :edit, params: { group_id: group, id: runner }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to render_template(:edit)
+ end
+ end
+
+ context 'when user is not owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'renders a 404' do
+ get :edit, params: { group_id: group, id: runner }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
describe '#update' do
- it 'updates the runner and ticks the queue' do
- new_desc = runner.description.swapcase
+ context 'when user is an owner' do
+ before do
+ group.add_owner(user)
+ end
- expect do
- post :update, params: params.merge(runner: { description: new_desc } )
- end.to change { runner.ensure_runner_queue_value }
+ it 'updates the runner, ticks the queue, and redirects' do
+ new_desc = runner.description.swapcase
- runner.reload
+ expect do
+ post :update, params: params.merge(runner: { description: new_desc } )
+ end.to change { runner.ensure_runner_queue_value }
- expect(response).to have_gitlab_http_status(302)
- expect(runner.description).to eq(new_desc)
+ expect(response).to have_gitlab_http_status(302)
+ expect(runner.reload.description).to eq(new_desc)
+ end
+ end
+
+ context 'when user is not an owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'rejects the update and responds 404' do
+ old_desc = runner.description
+
+ expect do
+ post :update, params: params.merge(runner: { description: old_desc.swapcase } )
+ end.not_to change { runner.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(runner.reload.description).to eq(old_desc)
+ end
end
end
describe '#destroy' do
- it 'destroys the runner' do
- delete :destroy, params: params
+ context 'when user is an owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'destroys the runner and redirects' do
+ delete :destroy, params: params
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ end
+ end
+
+ context 'when user is not an owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'responds 404 and does not destroy the runner' do
+ delete :destroy, params: params
- expect(response).to have_gitlab_http_status(302)
- expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ expect(response).to have_gitlab_http_status(404)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_present
+ end
end
end
describe '#resume' do
- it 'marks the runner as active and ticks the queue' do
- runner.update(active: false)
+ context 'when user is an owner' do
+ before do
+ group.add_owner(user)
+ end
- expect do
- post :resume, params: params
- end.to change { runner.ensure_runner_queue_value }
+ it 'marks the runner as active, ticks the queue, and redirects' do
+ runner.update(active: false)
- runner.reload
+ expect do
+ post :resume, params: params
+ end.to change { runner.ensure_runner_queue_value }
- expect(response).to have_gitlab_http_status(302)
- expect(runner.active).to eq(true)
+ expect(response).to have_gitlab_http_status(302)
+ expect(runner.reload.active).to eq(true)
+ end
+ end
+
+ context 'when user is not an owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'responds 404 and does not activate the runner' do
+ runner.update(active: false)
+
+ expect do
+ post :resume, params: params
+ end.not_to change { runner.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(runner.reload.active).to eq(false)
+ end
end
end
describe '#pause' do
- it 'marks the runner as inactive and ticks the queue' do
- runner.update(active: true)
+ context 'when user is an owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'marks the runner as inactive, ticks the queue, and redirects' do
+ runner.update(active: true)
+
+ expect do
+ post :pause, params: params
+ end.to change { runner.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(runner.reload.active).to eq(false)
+ end
+ end
+
+ context 'when user is not an owner' do
+ before do
+ group.add_maintainer(user)
+ end
- expect do
- post :pause, params: params
- end.to change { runner.ensure_runner_queue_value }
+ it 'responds 404 and does not update the runner or queue' do
+ runner.update(active: true)
- runner.reload
+ expect do
+ post :pause, params: params
+ end.not_to change { runner.ensure_runner_queue_value }
- expect(response).to have_gitlab_http_status(302)
- expect(runner.active).to eq(false)
+ expect(response).to have_gitlab_http_status(404)
+ expect(runner.reload.active).to eq(true)
+ end
end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 187c7864ad7..608131dcbc8 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1084,16 +1084,41 @@ describe Projects::IssuesController do
end
it "deletes the issue" do
- delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, destroy_confirm: true }
expect(response).to have_gitlab_http_status(302)
expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./)
end
+ it "deletes the issue" do
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, destroy_confirm: true }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./)
+ end
+
+ it "prevents deletion if destroy_confirm is not set" do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(controller).to set_flash[:notice].to('Destroy confirmation not provided for issue')
+ end
+
+ it "prevents deletion in JSON format if destroy_confirm is not set" do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, format: 'json' }
+
+ expect(response).to have_gitlab_http_status(422)
+ expect(json_response).to eq({ 'errors' => 'Destroy confirmation not provided for issue' })
+ end
+
it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_target).with(issue).once
- delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, destroy_confirm: true }
end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 11b1eaf11b7..d0370dfaeee 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -573,16 +573,34 @@ describe Projects::MergeRequestsController do
end
it "deletes the merge request" do
- delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid, destroy_confirm: true }
expect(response).to have_gitlab_http_status(302)
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./)
end
+ it "prevents deletion if destroy_confirm is not set" do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(controller).to set_flash[:notice].to('Destroy confirmation not provided for merge request')
+ end
+
+ it "prevents deletion in JSON format if destroy_confirm is not set" do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid, format: 'json' }
+
+ expect(response).to have_gitlab_http_status(422)
+ expect(json_response).to eq({ 'errors' => 'Destroy confirmation not provided for merge request' })
+ end
+
it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_target).with(merge_request).once
- delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid, destroy_confirm: true }
end
end
end
@@ -719,19 +737,63 @@ describe Projects::MergeRequestsController do
end
describe 'GET test_reports' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_diffs,
+ :with_merge_request_pipeline,
+ target_project: project,
+ source_project: project
+ )
+ end
+
subject do
- get :test_reports,
- params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid
- },
- format: :json
+ get :test_reports, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid
+ },
+ format: :json
end
before do
allow_any_instance_of(MergeRequest)
- .to receive(:compare_test_reports).and_return(comparison_status)
+ .to receive(:compare_test_reports)
+ .and_return(comparison_status)
+
+ allow_any_instance_of(MergeRequest)
+ .to receive(:actual_head_pipeline)
+ .and_return(merge_request.all_pipelines.take)
+ end
+
+ describe 'permissions on a public project with private CI/CD' do
+ let(:project) { create :project, :repository, :public, :builds_private }
+ let(:comparison_status) { { status: :parsed, data: { summary: 1 } } }
+
+ context 'while signed out' do
+ before do
+ sign_out(user)
+ end
+
+ it 'responds with a 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(response.body).to be_blank
+ end
+ end
+
+ context 'while signed in as an unrelated user' do
+ before do
+ sign_in(create(:user))
+ end
+
+ it 'responds with a 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(response.body).to be_blank
+ end
+ end
end
context 'when comparison is being processed' do
@@ -1052,17 +1114,39 @@ describe Projects::MergeRequestsController do
let(:status) { pipeline.detailed_status(double('user')) }
- before do
+ it 'returns a detailed head_pipeline status in json' do
get_pipeline_status
- end
- it 'return a detailed head_pipeline status in json' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
end
+
+ context 'with project member visibility on a public project' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, :public, :builds_private) }
+
+ it 'returns pipeline data to project members' do
+ project.add_developer(user)
+
+ get_pipeline_status
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['text']).to eq status.text
+ expect(json_response['label']).to eq status.label
+ expect(json_response['icon']).to eq status.icon
+ expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
+ end
+
+ it 'returns blank OK response to non-project-members' do
+ get_pipeline_status
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_empty
+ end
+ end
end
context 'when head_pipeline does not exist' do
@@ -1070,7 +1154,7 @@ describe Projects::MergeRequestsController do
get_pipeline_status
end
- it 'return empty' do
+ it 'returns blank OK response' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_empty
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 4500c412521..4db77921f24 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -212,40 +212,232 @@ describe Projects::NotesController do
describe 'POST create' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
+ let(:note_text) { 'some note' }
let(:request_params) do
{
- note: { note: 'some note', noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
+ note: { note: note_text, noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
namespace_id: project.namespace,
project_id: project,
merge_request_diff_head_sha: 'sha',
target_type: 'merge_request',
target_id: merge_request.id
- }
+ }.merge(extra_request_params)
+ end
+ let(:extra_request_params) { {} }
+
+ let(:project_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:merge_requests_access_level) { ProjectFeature::ENABLED }
+
+ def create!
+ post :create, params: request_params
end
before do
+ project.update_attribute(:visibility_level, project_visibility)
+ project.project_feature.update(merge_requests_access_level: merge_requests_access_level)
sign_in(user)
- project.add_developer(user)
end
- it "returns status 302 for html" do
- post :create, params: request_params
+ describe 'making the creation request' do
+ before do
+ create!
+ end
+
+ context 'the project is publically available' do
+ context 'for HTML' do
+ it "returns status 302" do
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'for JSON' do
+ let(:extra_request_params) { { format: :json } }
+
+ it "returns status 200 for json" do
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
- expect(response).to have_gitlab_http_status(302)
+ context 'the project is a private project' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PRIVATE }
+
+ [{}, { format: :json }].each do |extra|
+ context "format is #{extra[:format]}" do
+ let(:extra_request_params) { extra }
+
+ it "returns status 404" do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
end
- it "returns status 200 for json" do
- post :create, params: request_params.merge(format: :json)
+ context 'the user is a developer on a private project' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PRIVATE }
- expect(response).to have_gitlab_http_status(200)
+ before do
+ project.add_developer(user)
+ end
+
+ context 'HTML requests' do
+ it "returns status 302 (redirect)" do
+ create!
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'JSON requests' do
+ let(:extra_request_params) { { format: :json } }
+
+ it "returns status 200" do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'the return_discussion param is set' do
+ let(:extra_request_params) { { format: :json, return_discussion: 'true' } }
+
+ it 'returns discussion JSON when the return_discussion param is set' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to have_key 'discussion'
+ expect(json_response.dig('discussion', 'notes', 0, 'note')).to eq(request_params[:note][:note])
+ end
+ end
+
+ context 'when creating a note with quick actions' do
+ context 'with commands that return changes' do
+ let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
+ let(:extra_request_params) { { format: :json } }
+
+ it 'includes changes in commands_changes ' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+
+ context 'with commands that do not return changes' do
+ let(:issue) { create(:issue, project: project) }
+ let(:other_project) { create(:project) }
+ let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
+ let(:extra_request_params) { { format: :json, target_id: issue.id, target_type: 'issue' } }
+
+ before do
+ other_project.add_developer(user)
+ end
+
+ it 'does not include changes in commands_changes' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+ end
end
- it 'returns discussion JSON when the return_discussion param is set' do
- post :create, params: request_params.merge(format: :json, return_discussion: 'true')
+ context 'when the internal project prohibits non-members from accessing merge requests' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::INTERNAL }
+ let(:merge_requests_access_level) { ProjectFeature::PRIVATE }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to have_key 'discussion'
- expect(json_response['discussion']['notes'][0]['note']).to eq(request_params[:note][:note])
+ it "prevents a non-member user from creating a note on one of the project's merge requests" do
+ create!
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'when the user is a team member' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'can add comments' do
+ expect { create! }.to change { project.notes.count }.by(1)
+ end
+ end
+
+ # Illustration of the attack vector for posting comments to discussions that should
+ # be inaccessible.
+ #
+ # This relies on posting a note to a commit that is not necessarily even in the
+ # merge request, with a value of :in_reply_to_discussion_id that points to a
+ # discussion on a merge_request that should not be accessible.
+ context 'when the request includes a :in_reply_to_discussion_id designed to fool us' do
+ let(:commit) { create(:commit, project: project) }
+
+ let(:existing_comment) do
+ create(:note_on_commit,
+ note: 'first',
+ project: project,
+ commit_id: merge_request.commit_shas.first)
+ end
+
+ let(:discussion) { existing_comment.discussion }
+
+ # see !60465 for details of the structure of this request
+ let(:request_params) do
+ { "utf8" => "✓",
+ "authenticity_token" => "1",
+ "view" => "inline",
+ "line_type" => "",
+ "merge_request_diff_head_sha" => "",
+ "in_reply_to_discussion_id" => discussion.id,
+ "note_project_id" => project.id,
+ "project_id" => project.id,
+ "namespace_id" => project.namespace,
+ "target_type" => "commit",
+ "target_id" => commit.id,
+ "note" => {
+ "noteable_type" => "",
+ "noteable_id" => "",
+ "commit_id" => "",
+ "type" => "",
+ "line_code" => "",
+ "position" => "",
+ "note" => "ThisReplyWillGoToMergeRequest"
+ } }
+ end
+
+ it 'prevents the request from adding notes to the spoofed discussion' do
+ expect { create! }.not_to change { discussion.notes.count }
+ end
+
+ it 'returns an error to the user' do
+ create!
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context 'when the public project prohibits non-members from accessing merge requests' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:merge_requests_access_level) { ProjectFeature::PRIVATE }
+
+ it "prevents a non-member user from creating a note on one of the project's merge requests" do
+ create!
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'when the user is a team member' do
+ before do
+ project.add_developer(user)
+ create!
+ end
+
+ it 'can add comments' do
+ expect(response).to be_redirect
+ end
+ end
end
context 'when merge_request_diff_head_sha present' do
@@ -262,7 +454,7 @@ describe Projects::NotesController do
end
it "returns status 302 for html" do
- post :create, params: request_params
+ create!
expect(response).to have_gitlab_http_status(302)
end
@@ -285,7 +477,7 @@ describe Projects::NotesController do
end
context 'when creating a commit comment from an MR fork' do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :public) }
let(:forked_project) do
fork_project(project, nil, repository: true)
@@ -299,45 +491,59 @@ describe Projects::NotesController do
create(:note_on_commit, note: 'a note', project: forked_project, commit_id: merge_request.commit_shas.first)
end
- def post_create(extra_params = {})
- post :create, params: {
+ let(:note_project_id) do
+ forked_project.id
+ end
+
+ let(:request_params) do
+ {
note: { note: 'some other note', noteable_id: merge_request.id },
namespace_id: project.namespace,
project_id: project,
target_type: 'merge_request',
target_id: merge_request.id,
- note_project_id: forked_project.id,
+ note_project_id: note_project_id,
in_reply_to_discussion_id: existing_comment.discussion_id
- }.merge(extra_params)
+ }
+ end
+
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+
+ before do
+ forked_project.update_attribute(:visibility_level, fork_visibility)
end
context 'when the note_project_id is not correct' do
- it 'returns a 404' do
- post_create(note_project_id: Project.maximum(:id).succ)
+ let(:note_project_id) do
+ project.id && Project.maximum(:id).succ
+ end
+ it 'returns a 404' do
+ create!
expect(response).to have_gitlab_http_status(404)
end
end
context 'when the user has no access to the fork' do
- it 'returns a 404' do
- post_create
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PRIVATE }
+ it 'returns a 404' do
+ create!
expect(response).to have_gitlab_http_status(404)
end
end
context 'when the user has access to the fork' do
- let(:discussion) { forked_project.notes.find_discussion(existing_comment.discussion_id) }
+ let!(:discussion) { forked_project.notes.find_discussion(existing_comment.discussion_id) }
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PUBLIC }
- before do
- forked_project.add_developer(user)
-
- existing_comment
+ it 'is successful' do
+ create!
+ expect(response).to have_gitlab_http_status(302)
end
it 'creates the note' do
- expect { post_create }.to change { forked_project.notes.count }.by(1)
+ expect { create! }.to change { forked_project.notes.count }.by(1)
end
end
end
@@ -346,11 +552,6 @@ describe Projects::NotesController do
let(:locked_issue) { create(:issue, :locked, project: project) }
let(:issue) {create(:issue, project: project)}
- before do
- project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
- project.project_member(user).destroy
- end
-
it 'uses target_id and ignores noteable_id' do
request_params = {
note: { note: 'some note', noteable_type: 'Issue', noteable_id: locked_issue.id },
@@ -368,7 +569,6 @@ describe Projects::NotesController do
context 'when the merge request discussion is locked' do
before do
- project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
merge_request.update_attribute(:discussion_locked, true)
end
@@ -382,6 +582,10 @@ describe Projects::NotesController do
end
context 'when a user is a team member' do
+ before do
+ project.add_developer(user)
+ end
+
it 'returns 302 status for html' do
post :create, params: request_params
@@ -400,10 +604,6 @@ describe Projects::NotesController do
end
context 'when a user is not a team member' do
- before do
- project.project_member(user).destroy
- end
-
it 'returns 404 status' do
post :create, params: request_params
@@ -415,37 +615,6 @@ describe Projects::NotesController do
end
end
end
-
- context 'when creating a note with quick actions' do
- context 'with commands that return changes' do
- let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
-
- it 'includes changes in commands_changes ' do
- post :create, params: request_params.merge(note: { note: note_text }, format: :json)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
- expect(json_response['commands_changes']).not_to include('target_project', 'title')
- end
- end
-
- context 'with commands that do not return changes' do
- let(:issue) { create(:issue, project: project) }
- let(:other_project) { create(:project) }
- let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
-
- before do
- other_project.add_developer(user)
- end
-
- it 'does not include changes in commands_changes' do
- post :create, params: request_params.merge(note: { note: note_text }, target_type: 'issue', target_id: issue.id, format: :json)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['commands_changes']).not_to include('target_project', 'title')
- end
- end
- end
end
describe 'PUT update' do
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 563b61962cf..180d997a8e8 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -11,6 +11,7 @@ describe Projects::ServicesController do
before do
sign_in(user)
project.add_maintainer(user)
+ allow(Gitlab::UrlBlocker).to receive(:validate!).and_return([URI.parse('http://example.com'), nil])
end
describe '#test' do
@@ -56,6 +57,8 @@ describe Projects::ServicesController do
stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
.to_return(status: 200, body: '{}')
+ expect(Gitlab::HTTP).to receive(:get).with("/rest/api/2/serverInfo", any_args).and_call_original
+
put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(response.status).to eq(200)
@@ -66,6 +69,8 @@ describe Projects::ServicesController do
stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
.to_return(status: 200, body: '{}')
+ expect(Gitlab::HTTP).to receive(:get).with("/rest/api/2/serverInfo", any_args).and_call_original
+
put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(response.status).to eq(200)
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 9c4ddce5409..68b7bf61231 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -100,16 +100,8 @@ describe SessionsController do
end
end
- context 'when reCAPTCHA is enabled' do
- let(:user) { create(:user) }
- let(:user_params) { { login: user.username, password: user.password } }
-
- before do
- stub_application_setting(recaptcha_enabled: true)
- request.headers[described_class::CAPTCHA_HEADER] = 1
- end
-
- it 'displays an error when the reCAPTCHA is not solved' do
+ context 'with reCAPTCHA' do
+ def unsuccesful_login(user_params, sesion_params: {})
# Without this, `verify_recaptcha` arbitrarily returns true in test env
Recaptcha.configuration.skip_verify_env.delete('test')
counter = double(:counter)
@@ -119,14 +111,10 @@ describe SessionsController do
.with(:failed_login_captcha_total, anything)
.and_return(counter)
- post(:create, params: { user: user_params })
-
- expect(response).to render_template(:new)
- expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
- expect(subject.current_user).to be_nil
+ post(:create, params: { user: user_params }, session: sesion_params)
end
- it 'successfully logs in a user when reCAPTCHA is solved' do
+ def succesful_login(user_params, sesion_params: {})
# Avoid test ordering issue and ensure `verify_recaptcha` returns true
Recaptcha.configuration.skip_verify_env << 'test'
counter = double(:counter)
@@ -137,9 +125,80 @@ describe SessionsController do
.and_return(counter)
expect(Gitlab::Metrics).to receive(:counter).and_call_original
- post(:create, params: { user: user_params })
+ post(:create, params: { user: user_params }, session: sesion_params)
+ end
- expect(subject.current_user).to eq user
+ context 'when reCAPTCHA is enabled' do
+ let(:user) { create(:user) }
+ let(:user_params) { { login: user.username, password: user.password } }
+
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ request.headers[described_class::CAPTCHA_HEADER] = 1
+ end
+
+ it 'displays an error when the reCAPTCHA is not solved' do
+ # Without this, `verify_recaptcha` arbitrarily returns true in test env
+
+ unsuccesful_login(user_params)
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ succesful_login(user_params)
+
+ expect(subject.current_user).to eq user
+ end
+ end
+
+ context 'when reCAPTCHA login protection is enabled' do
+ let(:user) { create(:user) }
+ let(:user_params) { { login: user.username, password: user.password } }
+
+ before do
+ stub_application_setting(login_recaptcha_protection_enabled: true)
+ end
+
+ context 'when user tried to login 5 times' do
+ it 'displays an error when the reCAPTCHA is not solved' do
+ unsuccesful_login(user_params, sesion_params: { failed_login_attempts: 6 })
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ succesful_login(user_params, sesion_params: { failed_login_attempts: 6 })
+
+ expect(subject.current_user).to eq user
+ end
+ end
+
+ context 'when there are more than 5 anonymous session with the same IP' do
+ before do
+ allow(Gitlab::AnonymousSession).to receive_message_chain(:new, :stored_sessions).and_return(6)
+ end
+
+ it 'displays an error when the reCAPTCHA is not solved' do
+ unsuccesful_login(user_params)
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ expect(Gitlab::AnonymousSession).to receive_message_chain(:new, :cleanup_session_per_ip_entries)
+
+ succesful_login(user_params)
+
+ expect(subject.current_user).to eq user
+ end
+ end
end
end
end
@@ -348,4 +407,17 @@ describe SessionsController do
expect(controller.stored_location_for(:redirect)).to eq(search_path)
end
end
+
+ context 'when login fails' do
+ before do
+ set_devise_mapping(context: @request)
+ @request.env["warden.options"] = { action: 'unauthenticated' }
+ end
+
+ it 'does increment failed login counts for session' do
+ get(:new, params: { user: { login: 'failed' } })
+
+ expect(session[:failed_login_attempts]).to eq(1)
+ end
+ end
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 0876502a899..5f4a6bf8ee7 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -21,8 +21,20 @@ shared_examples 'content publicly cached' do
end
describe UploadsController do
+ include WorkhorseHelpers
+
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
+ describe 'POST #authorize' do
+ it_behaves_like 'handle uploads authorize' do
+ let(:uploader_class) { PersonalFileUploader }
+ let(:model) { create(:personal_snippet, :public) }
+ let(:params) do
+ { model: 'personal_snippet', id: model.id }
+ end
+ end
+ end
+
describe 'POST create' do
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
@@ -636,4 +648,10 @@ describe UploadsController do
end
end
end
+
+ def post_authorize(verified: true)
+ request.headers.merge!(workhorse_internal_api_request_header) if verified
+
+ post :authorize, params: { model: 'personal_snippet', id: model.id }, format: :json
+ end
end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 4ad90c96558..0d5f5df71b6 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -282,10 +282,6 @@ describe "Admin Runners" do
visit admin_runner_path(runner)
end
- describe 'runner info' do
- it { expect(find_field('runner_token').value).to eq runner.token }
- end
-
describe 'projects' do
it 'contains project names' do
expect(page).to have_content(@project1.full_name)
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index ddd87404003..eb59de2e132 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -263,6 +263,7 @@ describe 'Admin updates settings' do
page.within('.as-spam') do
check 'Enable reCAPTCHA'
+ check 'Enable reCAPTCHA for login'
fill_in 'reCAPTCHA Site Key', with: 'key'
fill_in 'reCAPTCHA Private Key', with: 'key'
fill_in 'IPs per user', with: 15
@@ -271,6 +272,7 @@ describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
expect(current_settings.recaptcha_enabled).to be true
+ expect(current_settings.login_recaptcha_protection_enabled).to be true
expect(current_settings.unique_ips_limit_per_user).to eq(15)
end
end
diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
index f273e416597..efa163042f9 100644
--- a/spec/features/dashboard/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -31,9 +31,9 @@ describe 'Dashboard > User filters todos', :js do
end
it 'displays all todos without a filter' do
- expect(page).to have_content issue1.to_reference(full: true)
- expect(page).to have_content merge_request.to_reference(full: true)
- expect(page).to have_content issue2.to_reference(full: true)
+ expect(page).to have_content issue1.to_reference(full: false)
+ expect(page).to have_content merge_request.to_reference(full: false)
+ expect(page).to have_content issue2.to_reference(full: false)
end
it 'filters by project' do
@@ -58,9 +58,9 @@ describe 'Dashboard > User filters todos', :js do
wait_for_requests
- expect(page).to have_content issue1.to_reference(full: true)
- expect(page).to have_content merge_request.to_reference(full: true)
- expect(page).not_to have_content issue2.to_reference(full: true)
+ expect(page).to have_content "issue #{issue1.to_reference} \"issue\" at #{group1.name} / project_1"
+ expect(page).to have_content "merge request #{merge_request.to_reference}"
+ expect(page).not_to have_content "issue #{issue2.to_reference} \"issue\" at #{group2.name} / project_3"
end
context 'Author filter' do
diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb
index 3870c661784..421a66c6d48 100644
--- a/spec/features/dashboard/todos/todos_sorting_spec.rb
+++ b/spec/features/dashboard/todos/todos_sorting_spec.rb
@@ -42,33 +42,33 @@ describe 'Dashboard > User sorts todos' do
click_link 'Last created'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content('merge_request_1')
- expect(results_list.all('p')[1]).to have_content('issue_1')
- expect(results_list.all('p')[2]).to have_content('issue_3')
- expect(results_list.all('p')[3]).to have_content('issue_2')
- expect(results_list.all('p')[4]).to have_content('issue_4')
+ expect(results_list.all('.todo-title')[0]).to have_content('merge_request_1')
+ expect(results_list.all('.todo-title')[1]).to have_content('issue_1')
+ expect(results_list.all('.todo-title')[2]).to have_content('issue_3')
+ expect(results_list.all('.todo-title')[3]).to have_content('issue_2')
+ expect(results_list.all('.todo-title')[4]).to have_content('issue_4')
end
it 'sorts with newest created todos first' do
click_link 'Oldest created'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content('issue_4')
- expect(results_list.all('p')[1]).to have_content('issue_2')
- expect(results_list.all('p')[2]).to have_content('issue_3')
- expect(results_list.all('p')[3]).to have_content('issue_1')
- expect(results_list.all('p')[4]).to have_content('merge_request_1')
+ expect(results_list.all('.todo-title')[0]).to have_content('issue_4')
+ expect(results_list.all('.todo-title')[1]).to have_content('issue_2')
+ expect(results_list.all('.todo-title')[2]).to have_content('issue_3')
+ expect(results_list.all('.todo-title')[3]).to have_content('issue_1')
+ expect(results_list.all('.todo-title')[4]).to have_content('merge_request_1')
end
it 'sorts by label priority' do
click_link 'Label priority'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content('issue_3')
- expect(results_list.all('p')[1]).to have_content('merge_request_1')
- expect(results_list.all('p')[2]).to have_content('issue_1')
- expect(results_list.all('p')[3]).to have_content('issue_2')
- expect(results_list.all('p')[4]).to have_content('issue_4')
+ expect(results_list.all('.todo-title')[0]).to have_content('issue_3')
+ expect(results_list.all('.todo-title')[1]).to have_content('merge_request_1')
+ expect(results_list.all('.todo-title')[2]).to have_content('issue_1')
+ expect(results_list.all('.todo-title')[3]).to have_content('issue_2')
+ expect(results_list.all('.todo-title')[4]).to have_content('issue_4')
end
end
@@ -93,9 +93,9 @@ describe 'Dashboard > User sorts todos' do
click_link 'Label priority'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content('issue_1')
- expect(results_list.all('p')[1]).to have_content('issue_2')
- expect(results_list.all('p')[2]).to have_content('merge_request_1')
+ expect(results_list.all('.todo-title')[0]).to have_content('issue_1')
+ expect(results_list.all('.todo-title')[1]).to have_content('issue_2')
+ expect(results_list.all('.todo-title')[2]).to have_content('merge_request_1')
end
end
end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index b98a04b0bda..867281da1e6 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -3,10 +3,10 @@
require 'spec_helper'
describe 'Dashboard Todos' do
- let(:user) { create(:user) }
+ let(:user) { create(:user, username: 'john') }
let(:author) { create(:user) }
let(:project) { create(:project, :public) }
- let(:issue) { create(:issue, due_date: Date.today) }
+ let(:issue) { create(:issue, due_date: Date.today, title: "Fix bug") }
context 'User does not have todos' do
before do
@@ -135,7 +135,7 @@ describe 'Dashboard Todos' do
it 'shows issue assigned to yourself message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself")
+ expect(page).to have_content("You assigned issue #{issue.to_reference} \"Fix bug\" at #{project.namespace.owner_name} / #{project.name} to yourself")
end
end
end
@@ -148,7 +148,7 @@ describe 'Dashboard Todos' do
it 'shows you added a todo message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You added a todo for issue #{issue.to_reference} \"Fix bug\" at #{project.namespace.owner_name} / #{project.name}")
expect(page).not_to have_content('to yourself')
end
end
@@ -162,7 +162,7 @@ describe 'Dashboard Todos' do
it 'shows you mentioned yourself message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference} \"Fix bug\" at #{project.namespace.owner_name} / #{project.name}")
expect(page).not_to have_content('to yourself')
end
end
@@ -176,14 +176,14 @@ describe 'Dashboard Todos' do
it 'shows you directly addressed yourself message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference} \"Fix bug\" at #{project.namespace.owner_name} / #{project.name}")
expect(page).not_to have_content('to yourself')
end
end
end
context 'approval todo' do
- let(:merge_request) { create(:merge_request) }
+ let(:merge_request) { create(:merge_request, title: "Fixes issue") }
before do
create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
@@ -192,7 +192,7 @@ describe 'Dashboard Todos' do
it 'shows you set yourself as an approver message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
+ expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference} \"Fixes issue\" at #{project.namespace.owner_name} / #{project.name}")
expect(page).not_to have_content('to yourself')
end
end
@@ -354,7 +354,7 @@ describe 'Dashboard Todos' do
it 'links to the pipelines for the merge request' do
href = pipelines_project_merge_request_path(project, todo.target)
- expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href: href
+ expect(page).to have_link "merge request #{todo.target.to_reference}", href: href
end
end
end
diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb
index 68d99b4241a..76eef66c517 100644
--- a/spec/features/markdown/math_spec.rb
+++ b/spec/features/markdown/math_spec.rb
@@ -34,7 +34,9 @@ describe 'Math rendering', :js do
visit project_issue_path(project, issue)
- expect(page).to have_selector('.katex-error', text: "\href{javascript:alert('xss');}{xss}")
- expect(page).to have_selector('.katex-html a', text: 'Gitlab')
+ page.within '.description > .md' do
+ expect(page).to have_selector('.katex-error')
+ expect(page).to have_selector('.katex-html a', text: 'Gitlab')
+ end
end
end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 1ab7742b36e..0905ab0aef8 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -49,6 +49,23 @@ describe 'User edit profile' do
end
end
+ describe 'when I change my email' do
+ before do
+ user.send_reset_password_instructions
+ end
+
+ it 'clears the reset password token' do
+ expect(user.reset_password_token?).to be true
+
+ fill_in 'user_email', with: 'new-email@example.com'
+ submit_settings
+
+ user.reload
+ expect(user.confirmation_token).not_to be_nil
+ expect(user.reset_password_token?).to be false
+ end
+ end
+
context 'user avatar' do
before do
attach_file(:user_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index a090461261b..0b3f905b5de 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -14,7 +14,6 @@ describe "User browses files" do
before do
stub_feature_flags(vue_file_list: false)
- stub_feature_flags(csslab: false)
sign_in(user)
end
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 4203f58fe81..6920fb4e572 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -17,11 +17,10 @@ describe MembersFinder, '#execute' do
result = described_class.new(project, user2).execute
- expect(result.to_a).to match_array([member1, member2, member3])
+ expect(result).to contain_exactly(member1, member2, member3)
end
- it 'includes nested group members if asked' do
- project = create(:project, namespace: group)
+ it 'includes nested group members if asked', :nested_groups do
nested_group.request_access(user1)
member1 = group.add_maintainer(user2)
member2 = nested_group.add_maintainer(user3)
@@ -29,7 +28,28 @@ describe MembersFinder, '#execute' do
result = described_class.new(project, user2).execute(include_descendants: true)
- expect(result.to_a).to match_array([member1, member2, member3])
+ expect(result).to contain_exactly(member1, member2, member3)
+ end
+
+ it 'returns the members.access_level when the user is invited', :nested_groups do
+ member_invite = create(:project_member, :invited, project: project, invite_email: create(:user).email)
+ member1 = group.add_maintainer(user2)
+
+ result = described_class.new(project, user2).execute(include_descendants: true)
+
+ expect(result).to contain_exactly(member1, member_invite)
+ expect(result.last.access_level).to eq(member_invite.access_level)
+ end
+
+ it 'returns the highest access_level for the user', :nested_groups do
+ member1 = project.add_guest(user1)
+ group.add_developer(user1)
+ nested_group.add_reporter(user1)
+
+ result = described_class.new(project, user1).execute(include_descendants: true)
+
+ expect(result).to contain_exactly(member1)
+ expect(result.first.access_level).to eq(Gitlab::Access::DEVELOPER)
end
context 'when include_invited_groups_members == true' do
@@ -37,8 +57,8 @@ describe MembersFinder, '#execute' do
set(:linked_group) { create(:group, :public, :access_requestable) }
set(:nested_linked_group) { create(:group, parent: linked_group) }
- set(:linked_group_member) { linked_group.add_developer(user1) }
- set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) }
+ set(:linked_group_member) { linked_group.add_guest(user1) }
+ set(:nested_linked_group_member) { nested_linked_group.add_guest(user2) }
it 'includes all the invited_groups members including members inherited from ancestor groups' do
create(:project_group_link, project: project, group: nested_linked_group)
@@ -60,5 +80,17 @@ describe MembersFinder, '#execute' do
expect(subject).to contain_exactly(linked_group_member)
end
+
+ context 'when the user is a member of invited group and ancestor groups' do
+ it 'returns the highest access_level for the user limited by project_group_link.group_access', :nested_groups do
+ create(:project_group_link, project: project, group: nested_linked_group, group_access: Gitlab::Access::REPORTER)
+ nested_linked_group.add_developer(user1)
+
+ result = subject
+
+ expect(result).to contain_exactly(linked_group_member, nested_linked_group_member)
+ expect(result.first.access_level).to eq(Gitlab::Access::REPORTER)
+ end
+ end
end
end
diff --git a/spec/fixtures/api/schemas/entities/merge_request_noteable.json b/spec/fixtures/api/schemas/entities/merge_request_noteable.json
new file mode 100644
index 00000000000..88b0fecc24c
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_noteable.json
@@ -0,0 +1,28 @@
+{
+ "type": "object",
+ "properties" : {
+ "merge_params": { "type": ["object", "null"] },
+ "state": { "type": "string" },
+ "source_branch": { "type": "string" },
+ "target_branch": { "type": "string" },
+ "diff_head_sha": { "type": "string" },
+ "create_note_path": { "type": ["string", "null"] },
+ "preview_note_path": { "type": ["string", "null"] },
+ "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
+ "new_blob_path": { "type": ["string", "null"] },
+ "can_receive_suggestion": { "type": "boolean" },
+ "current_user": {
+ "type": "object",
+ "required": [
+ "can_create_note",
+ "can_update"
+ ],
+ "properties": {
+ "can_create_note": { "type": "boolean" },
+ "can_update": { "type": "boolean" }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
index 2052892dfa3..1eda0e12920 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
@@ -24,22 +24,20 @@
"ci_status": { "type": ["string", "null"] },
"cancel_auto_merge_path": { "type": ["string", "null"] },
"test_reports_path": { "type": ["string", "null"] },
- "can_receive_suggestion": { "type": "boolean" },
"create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
"current_user": {
"type": "object",
"required": [
"can_remove_source_branch",
"can_revert_on_current_merge_request",
- "can_cherry_pick_on_current_merge_request"
+ "can_cherry_pick_on_current_merge_request",
+ "can_create_issue"
],
"properties": {
"can_remove_source_branch": { "type": "boolean" },
"can_revert_on_current_merge_request": { "type": ["boolean", "null"] },
"can_cherry_pick_on_current_merge_request": { "type": ["boolean", "null"] },
- "can_create_note": { "type": "boolean" },
- "can_create_issue": { "type": "boolean" },
- "can_update": { "type": "boolean" }
+ "can_create_issue": { "type": "boolean" }
},
"additionalProperties": false
},
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 779a47222b7..e2df7952d8f 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -5,7 +5,6 @@
{ "$ref": "merge_request_poll_widget.json" },
{
"properties" : {
- "merge_params": { "type": ["object", "null"] },
"source_project_full_path": { "type": ["string", "null"]},
"target_project_full_path": { "type": ["string", "null"]},
"email_patches_path": { "type": "string" },
@@ -13,9 +12,7 @@
"merge_request_basic_path": { "type": "string" },
"merge_request_widget_path": { "type": "string" },
"merge_request_cached_widget_path": { "type": "string" },
- "create_note_path": { "type": ["string", "null"] },
"commit_change_content_path": { "type": "string" },
- "preview_note_path": { "type": ["string", "null"] },
"conflicts_docs_path": { "type": ["string", "null"] },
"merge_request_pipelines_docs_path": { "type": ["string", "null"] },
"ci_environments_status_path": { "type": "string" },
diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js
index 9f127ccb690..41da4125a20 100644
--- a/spec/frontend/clusters/components/application_row_spec.js
+++ b/spec/frontend/clusters/components/application_row_spec.js
@@ -371,7 +371,7 @@ describe('Application Row', () => {
it('contains a link to the chart repo if application has been updated', () => {
const version = '0.1.45';
- const chartRepo = 'https://gitlab.com/charts/gitlab-runner';
+ const chartRepo = 'https://gitlab.com/gitlab-org/charts/gitlab-runner';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.INSTALLED,
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
index f2cc413512d..c168bce7a4e 100644
--- a/spec/frontend/clusters/stores/clusters_store_spec.js
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -86,7 +86,7 @@ describe('Clusters Store', () => {
requestReason: null,
version: mockResponseData.applications[2].version,
updateAvailable: mockResponseData.applications[2].update_available,
- chartRepo: 'https://gitlab.com/charts/gitlab-runner',
+ chartRepo: 'https://gitlab.com/gitlab-org/charts/gitlab-runner',
installed: false,
installFailed: false,
updateFailed: false,
diff --git a/spec/frontend/notes/components/note_app_spec.js b/spec/frontend/notes/components/note_app_spec.js
index ff833d2c899..02fd30d5a15 100644
--- a/spec/frontend/notes/components/note_app_spec.js
+++ b/spec/frontend/notes/components/note_app_spec.js
@@ -133,32 +133,31 @@ describe('note_app', () => {
);
});
- it('should not render form when commenting is disabled', () => {
- wrapper.destroy();
+ it('should render form comment button as disabled', () => {
+ expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
+ });
- store.state.commentsDisabled = true;
- wrapper = mountComponent();
- return waitForDiscussionsRequest().then(() => {
- expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
- });
+ it('updates discussions badge', () => {
+ expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
});
+ });
- it('should render discussion filter note `commentsDisabled` is true', () => {
- wrapper.destroy();
+ describe('render with comments disabled', () => {
+ beforeEach(() => {
+ setFixtures('<div class="js-discussions-count"></div>');
+ Vue.http.interceptors.push(mockData.individualNoteInterceptor);
store.state.commentsDisabled = true;
wrapper = mountComponent();
- return waitForDiscussionsRequest().then(() => {
- expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
- });
+ return waitForDiscussionsRequest();
});
- it('should render form comment button as disabled', () => {
- expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
+ it('should not render form when commenting is disabled', () => {
+ expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
});
- it('updates discussions badge', () => {
- expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
+ it('should render discussion filter note `commentsDisabled` is true', () => {
+ expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
});
});
diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js
index 0e862c683d3..7c98a1a66c9 100644
--- a/spec/frontend/tracking_spec.js
+++ b/spec/frontend/tracking_spec.js
@@ -1,20 +1,56 @@
import $ from 'jquery';
import { setHTMLFixture } from './helpers/fixtures';
-import Tracking from '~/tracking';
+import Tracking, { initUserTracking } from '~/tracking';
describe('Tracking', () => {
+ let snowplowSpy;
+
beforeEach(() => {
window.snowplow = window.snowplow || (() => {});
+ window.snowplowOptions = {
+ namespace: '_namespace_',
+ hostname: 'app.gitfoo.com',
+ cookieDomain: '.gitfoo.com',
+ };
+ snowplowSpy = jest.spyOn(window, 'snowplow');
});
- describe('.event', () => {
- let snowplowSpy = null;
+ describe('initUserTracking', () => {
+ it('calls through to get a new tracker with the expected options', () => {
+ initUserTracking();
+ expect(snowplowSpy).toHaveBeenCalledWith('newTracker', '_namespace_', 'app.gitfoo.com', {
+ namespace: '_namespace_',
+ hostname: 'app.gitfoo.com',
+ cookieDomain: '.gitfoo.com',
+ appId: '',
+ userFingerprint: false,
+ respectDoNotTrack: true,
+ forceSecureTracker: true,
+ eventMethod: 'post',
+ contexts: { webPage: true },
+ activityTrackingEnabled: false,
+ pageTrackingEnabled: false,
+ });
+ });
- beforeEach(() => {
- snowplowSpy = jest.spyOn(window, 'snowplow');
+ it('should activate features based on what has been enabled', () => {
+ initUserTracking();
+ expect(snowplowSpy).not.toHaveBeenCalledWith('enableActivityTracking', 30, 30);
+ expect(snowplowSpy).not.toHaveBeenCalledWith('trackPageView');
+
+ window.snowplowOptions = Object.assign({}, window.snowplowOptions, {
+ activityTrackingEnabled: true,
+ pageTrackingEnabled: true,
+ });
+
+ initUserTracking();
+ expect(snowplowSpy).toHaveBeenCalledWith('enableActivityTracking', 30, 30);
+ expect(snowplowSpy).toHaveBeenCalledWith('trackPageView');
});
+ });
+ describe('.event', () => {
afterEach(() => {
window.doNotTrack = undefined;
navigator.doNotTrack = undefined;
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
new file mode 100644
index 00000000000..1f4d1e17ea0
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -0,0 +1,55 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import AutoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+
+describe('MRWidgetAutoMergeFailed', () => {
+ let wrapper;
+ const mergeError = 'This is the merge error';
+ const findButton = () => wrapper.find('button');
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(AutoMergeFailedComponent, {
+ sync: false,
+ propsData: { ...props },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent({
+ mr: { mergeError },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders failed message', () => {
+ expect(wrapper.text()).toContain('This merge request failed to be merged automatically');
+ });
+
+ it('renders merge error provided', () => {
+ expect(wrapper.text()).toContain(mergeError);
+ });
+
+ it('render refresh button', () => {
+ expect(findButton().text()).toEqual('Refresh');
+ });
+
+ it('emits event and shows loading icon when button is clicked', () => {
+ jest.spyOn(eventHub, '$emit');
+ findButton().trigger('click');
+
+ expect(eventHub.$emit.mock.calls[0][0]).toBe('MRWidgetUpdateRequested');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findButton().attributes('disabled')).toEqual('disabled');
+ expect(
+ findButton()
+ .find(GlLoadingIcon)
+ .exists(),
+ ).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js
new file mode 100644
index 00000000000..328eec0a80a
--- /dev/null
+++ b/spec/frontend/vue_shared/components/file_icon_spec.js
@@ -0,0 +1,75 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+describe('File Icon component', () => {
+ let wrapper;
+ const findIcon = () => wrapper.find('svg');
+ const getIconName = () =>
+ findIcon()
+ .find('use')
+ .element.getAttribute('xlink:href')
+ .replace(`${gon.sprite_file_icons}#`, '');
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(FileIcon, {
+ sync: false,
+ propsData: { ...props },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render a span element and an icon', () => {
+ createComponent({
+ fileName: 'test.js',
+ });
+
+ expect(wrapper.element.tagName).toEqual('SPAN');
+ expect(findIcon().exists()).toBeDefined();
+ });
+
+ it.each`
+ fileName | iconName
+ ${'test.js'} | ${'javascript'}
+ ${'test.png'} | ${'image'}
+ ${'webpack.js'} | ${'webpack'}
+ `('should render a $iconName icon based on file ending', ({ fileName, iconName }) => {
+ createComponent({ fileName });
+ expect(getIconName()).toBe(iconName);
+ });
+
+ it('should render a standard folder icon', () => {
+ createComponent({
+ fileName: 'js',
+ folder: true,
+ });
+
+ expect(findIcon().exists()).toBe(false);
+ expect(wrapper.find(Icon).props('cssClasses')).toContain('folder-icon');
+ });
+
+ it('should render a loading icon', () => {
+ createComponent({
+ fileName: 'test.js',
+ loading: true,
+ });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('should add a special class and a size class', () => {
+ const size = 120;
+ createComponent({
+ fileName: 'test.js',
+ cssClasses: 'extraclasses',
+ size,
+ });
+
+ expect(findIcon().classes()).toContain(`s${size}`);
+ expect(findIcon().classes()).toContain('extraclasses');
+ });
+});
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index d25f0c6de4a..a14ae2cde4b 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -6,30 +6,62 @@ describe EmailsHelper do
let(:merge_request) { create(:merge_request) }
let(:merge_request_presenter) { merge_request.present }
- context "and format is text" do
- it "returns plain text" do
- expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ context 'when user can read merge request' do
+ let(:user) { create(:user) }
+
+ before do
+ merge_request.project.add_developer(user)
+ self.instance_variable_set(:@recipient, user)
+ self.instance_variable_set(:@project, merge_request.project)
+ end
+
+ context "and format is text" do
+ it "returns plain text" do
+ expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ end
end
- end
- context "and format is HTML" do
- it "returns HTML" do
- expect(closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
+ context "and format is HTML" do
+ it "returns HTML" do
+ expect(helper.closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
+ end
+ end
+
+ context "and format is unknown" do
+ it "returns plain text" do
+ expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ end
end
end
- context "and format is unknown" do
- it "returns plain text" do
- expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ context 'when user cannot read merge request' do
+ it "does not have link to merge request" do
+ expect(helper.closure_reason_text(merge_request)).to be_empty
end
end
end
context 'when given a String' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" }
- it "returns plain text" do
- expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
+ context 'when user can read commits' do
+ before do
+ project.add_developer(user)
+ self.instance_variable_set(:@recipient, user)
+ self.instance_variable_set(:@project, project)
+ end
+
+ it "returns plain text" do
+ expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
+ end
+ end
+
+ context 'when user cannot read commits' do
+ it "returns plain text" do
+ expect(closure_reason_text(closed_via)).to be_empty
+ end
end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 4f1cab38f34..1d57aaa0da5 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -278,4 +278,14 @@ describe LabelsHelper do
it { is_expected.to eq('Subscribe at group level') }
end
end
+
+ describe '#label_tooltip_title' do
+ let(:html) { '<img src="example.png">This is an image</img>' }
+ let(:label_with_html_content) { create(:label, title: 'test', description: html) }
+
+ it 'removes HTML' do
+ tooltip = label_tooltip_title(label_with_html_content)
+ expect(tooltip).to eq('This is an image')
+ end
+ end
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index f6e1720e113..1757ec8fa4d 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -65,6 +65,9 @@ describe MarkupHelper do
describe 'inside a group' do
before do
+ # Ensure the generated reference links aren't redacted
+ group.add_maintainer(user)
+
helper.instance_variable_set(:@group, group)
helper.instance_variable_set(:@project, nil)
end
@@ -78,6 +81,9 @@ describe MarkupHelper do
let(:project_in_group) { create(:project, group: group) }
before do
+ # Ensure the generated reference links aren't redacted
+ project_in_group.add_maintainer(user)
+
helper.instance_variable_set(:@group, group)
helper.instance_variable_set(:@project, project_in_group)
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index a70bfc2adc7..d2a4ce6540d 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -503,7 +503,7 @@ describe ProjectsHelper do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:enabled_git_access_protocol) { 'ssh' }
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return('git@localhost:')
- expect(helper.push_to_create_project_command(user)).to eq('git push --set-upstream git@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)')
+ expect(helper.push_to_create_project_command(user)).to eq("git push --set-upstream #{Gitlab.config.gitlab.user}@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)")
end
end
@@ -549,6 +549,42 @@ describe ProjectsHelper do
end
end
+ describe '#git_user_email' do
+ context 'not logged-in' do
+ before do
+ allow(helper).to receive(:current_user).and_return(nil)
+ end
+
+ it 'returns your@email.com' do
+ expect(helper.send(:git_user_email)).to eq('your@email.com')
+ end
+ end
+
+ context 'user logged in' do
+ let(:user) { create(:user) }
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ context 'user has no configured commit email' do
+ it 'returns the primary email' do
+ expect(helper.send(:git_user_email)).to eq(user.email)
+ end
+ end
+
+ context 'user has a configured commit email' do
+ before do
+ confirmed_email = create(:email, :confirmed, user: user)
+ user.update(commit_email: confirmed_email)
+ end
+
+ it 'returns the commit email' do
+ expect(helper.send(:git_user_email)).to eq(user.commit_email)
+ end
+ end
+ end
+ end
+
describe 'show_xcode_link' do
let!(:project) { create(:project) }
let(:mac_ua) { 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36' }
diff --git a/spec/initializers/asset_proxy_setting_spec.rb b/spec/initializers/asset_proxy_setting_spec.rb
new file mode 100644
index 00000000000..42e4d4aa594
--- /dev/null
+++ b/spec/initializers/asset_proxy_setting_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe 'Asset proxy settings initialization' do
+ describe '#asset_proxy' do
+ it 'defaults to disabled' do
+ expect(Banzai::Filter::AssetProxyFilter).to receive(:initialize_settings)
+
+ require_relative '../../config/initializers/asset_proxy_settings'
+
+ expect(Gitlab.config.asset_proxy.enabled).to be_falsey
+ end
+ end
+end
diff --git a/spec/initializers/rest-client-hostname_override_spec.rb b/spec/initializers/rest-client-hostname_override_spec.rb
new file mode 100644
index 00000000000..3707e001d41
--- /dev/null
+++ b/spec/initializers/rest-client-hostname_override_spec.rb
@@ -0,0 +1,147 @@
+require 'spec_helper'
+
+describe 'rest-client dns rebinding protection' do
+ include StubRequests
+
+ context 'when local requests are not allowed' do
+ it 'allows an external request with http' do
+ request_stub = stub_full_request('http://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('http://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request with https' do
+ request_stub = stub_full_request('https://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'raises error when it is a request that resolves to a local address' do
+ stub_full_request('https://example.com', ip_address: '172.16.0.0')
+
+ expect { RestClient.get('https://example.com') }
+ .to raise_error(ArgumentError,
+ "URL 'https://example.com' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request that resolves to a localhost address' do
+ stub_full_request('https://example.com', ip_address: '127.0.0.1')
+
+ expect { RestClient.get('https://example.com') }
+ .to raise_error(ArgumentError,
+ "URL 'https://example.com' is blocked: Requests to localhost are not allowed")
+ end
+
+ it 'raises error when it is a request to local address' do
+ expect { RestClient.get('http://172.16.0.0') }
+ .to raise_error(ArgumentError,
+ "URL 'http://172.16.0.0' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request to localhost address' do
+ expect { RestClient.get('http://127.0.0.1') }
+ .to raise_error(ArgumentError,
+ "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed")
+ end
+ end
+
+ context 'when port different from URL scheme is used' do
+ it 'allows the request' do
+ request_stub = stub_full_request('https://example.com:8080', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com:8080/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'raises error when it is a request to local address' do
+ expect { RestClient.get('https://172.16.0.0:8080') }
+ .to raise_error(ArgumentError,
+ "URL 'https://172.16.0.0:8080' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request to localhost address' do
+ expect { RestClient.get('https://127.0.0.1:8080') }
+ .to raise_error(ArgumentError,
+ "URL 'https://127.0.0.1:8080' is blocked: Requests to localhost are not allowed")
+ end
+ end
+
+ context 'when DNS rebinding protection is disabled' do
+ before do
+ stub_application_setting(dns_rebinding_protection_enabled: false)
+ end
+
+ it 'allows the request' do
+ request_stub = stub_request(:get, 'https://example.com')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+
+ context 'when http(s) proxy environment variable is set' do
+ before do
+ stub_env('https_proxy' => 'https://my.proxy')
+ end
+
+ it 'allows the request' do
+ request_stub = stub_request(:get, 'https://example.com')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+
+ context 'when local requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it 'allows an external request' do
+ request_stub = stub_full_request('https://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request that resolves to a local address' do
+ request_stub = stub_full_request('https://example.com', ip_address: '172.16.0.0')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request that resolves to a localhost address' do
+ request_stub = stub_full_request('https://example.com', ip_address: '127.0.0.1')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows a local address request' do
+ request_stub = stub_request(:get, 'http://172.16.0.0')
+
+ RestClient.get('http://172.16.0.0')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows a localhost address request' do
+ request_stub = stub_request(:get, 'http://127.0.0.1')
+
+ RestClient.get('http://127.0.0.1')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+end
diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js
index 388d7063d13..f9ee4648128 100644
--- a/spec/javascripts/environments/environment_item_spec.js
+++ b/spec/javascripts/environments/environment_item_spec.js
@@ -106,6 +106,7 @@ describe('Environment item', () => {
play_path: '/play',
},
],
+ deployed_at: '2016-11-29T18:11:58.430Z',
},
has_stop_action: true,
environment_path: 'root/ci-folders/environments/31',
@@ -139,9 +140,7 @@ describe('Environment item', () => {
it('should render last deployment date', () => {
const timeagoInstance = new timeago(); // eslint-disable-line
- const formatedDate = timeagoInstance.format(
- environment.last_deployment.deployable.created_at,
- );
+ const formatedDate = timeagoInstance.format(environment.last_deployment.deployed_at);
expect(
component.$el.querySelector('.environment-created-date-timeago').textContent,
diff --git a/spec/javascripts/issue_show/components/edit_actions_spec.js b/spec/javascripts/issue_show/components/edit_actions_spec.js
index d92c54ea83f..2ab74ae4e10 100644
--- a/spec/javascripts/issue_show/components/edit_actions_spec.js
+++ b/spec/javascripts/issue_show/components/edit_actions_spec.js
@@ -104,7 +104,7 @@ describe('Edit Actions components', () => {
spyOn(window, 'confirm').and.returnValue(true);
vm.$el.querySelector('.btn-danger').click();
- expect(eventHub.$emit).toHaveBeenCalledWith('delete.issuable');
+ expect(eventHub.$emit).toHaveBeenCalledWith('delete.issuable', { destroy_confirm: true });
});
it('shows loading icon after clicking delete button', done => {
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 5f81a168498..3812d46f838 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -8,7 +8,7 @@ export const notesDataMock = {
notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes',
quickActionsDocsPath: '/help/user/project/quick_actions',
registerPath: '/users/sign_in?redirect_to_referer=yes#register-pane',
- totalNotes: 1,
+ prerenderedNotesCount: 1,
closePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close',
reopenPath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen',
canAwardEmoji: true,
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
deleted file mode 100644
index 55a11a72551..00000000000
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
-import eventHub from '~/vue_merge_request_widget/event_hub';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('MRWidgetAutoMergeFailed', () => {
- let vm;
- const mergeError = 'This is the merge error';
-
- beforeEach(() => {
- const Component = Vue.extend(autoMergeFailedComponent);
- vm = mountComponent(Component, {
- mr: { mergeError },
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders failed message', () => {
- expect(vm.$el.textContent).toContain('This merge request failed to be merged automatically');
- });
-
- it('renders merge error provided', () => {
- expect(vm.$el.innerText).toContain(mergeError);
- });
-
- it('render refresh button', () => {
- expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Refresh');
- });
-
- it('emits event and shows loading icon when button is clicked', done => {
- spyOn(eventHub, '$emit');
- vm.$el.querySelector('button').click();
-
- expect(eventHub.$emit.calls.argsFor(0)[0]).toEqual('MRWidgetUpdateRequested');
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
- expect(vm.$el.querySelector('button .loading-container span').classList).toContain(
- 'gl-spinner',
- );
- done();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
deleted file mode 100644
index 1f61e19fa84..00000000000
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import Vue from 'vue';
-import fileIcon from '~/vue_shared/components/file_icon.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('File Icon component', () => {
- let vm;
- let FileIcon;
-
- beforeEach(() => {
- FileIcon = Vue.extend(fileIcon);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('should render a span element with an svg', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.js',
- });
-
- expect(vm.$el.tagName).toEqual('SPAN');
- expect(vm.$el.querySelector('span > svg')).toBeDefined();
- });
-
- it('should render a javascript icon based on file ending', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.js',
- });
-
- expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(
- `${gon.sprite_file_icons}#javascript`,
- );
- });
-
- it('should render a image icon based on file ending', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.png',
- });
-
- expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(
- `${gon.sprite_file_icons}#image`,
- );
- });
-
- it('should render a webpack icon based on file namer', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'webpack.js',
- });
-
- expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(
- `${gon.sprite_file_icons}#webpack`,
- );
- });
-
- it('should render a standard folder icon', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'js',
- folder: true,
- });
-
- expect(vm.$el.querySelector('span > svg > use').getAttribute('xlink:href')).toBe(
- `${gon.sprite_file_icons}#folder`,
- );
- });
-
- it('should render a loading icon', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.js',
- loading: true,
- });
-
- const { classList } = vm.$el.querySelector('.loading-container span');
-
- expect(classList.contains('gl-spinner')).toEqual(true);
- });
-
- it('should add a special class and a size class', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.js',
- cssClasses: 'extraclasses',
- size: 120,
- });
-
- const { classList } = vm.$el.firstChild;
- const containsSizeClass = classList.contains('s120');
- const containsCustomClass = classList.contains('extraclasses');
-
- expect(containsSizeClass).toBe(true);
- expect(containsCustomClass).toBe(true);
- });
-});
diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
new file mode 100644
index 00000000000..b7f45421b2a
--- /dev/null
+++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+describe Banzai::Filter::AssetProxyFilter do
+ include FilterSpecHelper
+
+ def image(path)
+ %(<img src="#{path}" />)
+ end
+
+ it 'does not replace if disabled' do
+ stub_asset_proxy_setting(enabled: false)
+
+ context = described_class.transform_context({})
+ src = 'http://example.com/test.png'
+ doc = filter(image(src), context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ context 'during initialization' do
+ after do
+ Gitlab.config.asset_proxy['enabled'] = false
+ end
+
+ it '#initialize_settings' do
+ stub_application_setting(asset_proxy_enabled: true)
+ stub_application_setting(asset_proxy_secret_key: 'shared-secret')
+ stub_application_setting(asset_proxy_url: 'https://assets.example.com')
+ stub_application_setting(asset_proxy_whitelist: %w(gitlab.com *.mydomain.com))
+
+ described_class.initialize_settings
+
+ expect(Gitlab.config.asset_proxy.enabled).to be_truthy
+ expect(Gitlab.config.asset_proxy.secret_key).to eq 'shared-secret'
+ expect(Gitlab.config.asset_proxy.url).to eq 'https://assets.example.com'
+ expect(Gitlab.config.asset_proxy.whitelist).to eq %w(gitlab.com *.mydomain.com)
+ expect(Gitlab.config.asset_proxy.domain_regexp).to eq /^(gitlab\.com|.*?\.mydomain\.com)$/i
+ end
+ end
+
+ context 'when properly configured' do
+ before do
+ stub_asset_proxy_setting(enabled: true)
+ stub_asset_proxy_setting(secret_key: 'shared-secret')
+ stub_asset_proxy_setting(url: 'https://assets.example.com')
+ stub_asset_proxy_setting(whitelist: %W(gitlab.com *.mydomain.com #{Gitlab.config.gitlab.host}))
+ stub_asset_proxy_setting(domain_regexp: described_class.compile_whitelist(Gitlab.config.asset_proxy.whitelist))
+ @context = described_class.transform_context({})
+ end
+
+ it 'replaces img src' do
+ src = 'http://example.com/test.png'
+ new_src = 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67'
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq new_src
+ expect(doc.at_css('img')['data-canonical-src']).to eq src
+ end
+
+ it 'skips internal images' do
+ src = "#{Gitlab.config.gitlab.url}/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skip relative urls' do
+ src = "/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips single domain' do
+ src = "http://gitlab.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips single domain and ignores url in query string' do
+ src = "http://gitlab.com/test.png?url=http://example.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips wildcarded domain' do
+ src = "http://images.mydomain.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 59fea5766ee..4b2500b31f7 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -156,6 +156,18 @@ describe Banzai::Filter::ExternalLinkFilter do
expect(doc_email.to_html).to include('http://xn--example-6p25f.com/</a>')
end
end
+
+ context 'autolinked image' do
+ let(:html) { %q(<a href="https://assets.example.com/6d8b/634c" data-canonical-src="http://exa%F0%9F%98%84mple.com/test.png"><img src="http://exa%F0%9F%98%84mple.com/test.png" data-canonical-src="http://exa%F0%9F%98%84mple.com/test.png"></a>) }
+ let(:doc) { filter(html) }
+
+ it_behaves_like 'an external link with rel attribute'
+
+ it 'adds a toolip with punycode' do
+ expect(doc.to_html).to include('class="has-tooltip"')
+ expect(doc.to_html).to include('title="http://xn--example-6p25f.com/test.png"')
+ end
+ end
end
context 'for links that look malicious' do
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index 7b0cb675551..011e3a1e2da 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -28,4 +28,11 @@ describe Banzai::Filter::ImageLinkFilter do
doc = filter(%Q(<p>test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline</p>))
expect(doc.to_html).to match %r{^<p>test <a[^>]*><img[^>]*></a> inline</p>$}
end
+
+ it 'keep the data-canonical-src' do
+ doc = filter(%q(<img src="http://assets.example.com/6cd/4d7" data-canonical-src="http://example.com/test.png" />))
+
+ expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
+ expect(doc.at_css('img')['data-canonical-src']).to eq doc.at_css('a')['data-canonical-src']
+ end
end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 213a5459118..35e99d2586e 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -10,6 +10,11 @@ describe Banzai::Filter::LabelReferenceFilter do
let(:label) { create(:label, project: project) }
let(:reference) { label.to_reference }
+ it_behaves_like 'HTML text with references' do
+ let(:resource) { label }
+ let(:resource_text) { resource.title }
+ end
+
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 3f021adc756..ab0c2c383c5 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -329,6 +329,10 @@ describe Banzai::Filter::MilestoneReferenceFilter do
it_behaves_like 'cross-project / same-namespace complete reference'
it_behaves_like 'cross project shorthand reference'
it_behaves_like 'references with HTML entities'
+ it_behaves_like 'HTML text with references' do
+ let(:resource) { milestone }
+ let(:resource_text) { "#{resource.class.reference_prefix}#{resource.title}" }
+ end
end
shared_context 'group milestones' do
@@ -340,6 +344,10 @@ describe Banzai::Filter::MilestoneReferenceFilter do
it_behaves_like 'String-based multi-word references in quotes'
it_behaves_like 'referencing a milestone in a link href'
it_behaves_like 'references with HTML entities'
+ it_behaves_like 'HTML text with references' do
+ let(:resource) { milestone }
+ let(:resource_text) { "#{resource.class.reference_prefix}#{resource.title}" }
+ end
it 'does not support references by IID' do
doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}")
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
index 69f9c1ae829..927d226c400 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -26,10 +26,18 @@ describe Banzai::Filter::ProjectReferenceFilter do
expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp))
end
- it 'fails fast for long invalid string' do
- expect do
- Timeout.timeout(5.seconds) { reference_filter("A" * 50000).to_html }
- end.not_to raise_error
+ context 'when invalid reference strings are very long' do
+ shared_examples_for 'fails fast' do |ref_string|
+ it 'fails fast for long strings' do
+ # took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172824
+ expect do
+ Timeout.timeout(3.seconds) { reference_filter(ref_string).to_html }
+ end.not_to raise_error
+ end
+ end
+
+ it_behaves_like 'fails fast', 'A' * 50000
+ it_behaves_like 'fails fast', '/a' * 50000
end
it 'allows references with text after the > character' do
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index ecb83b6cb66..789530fbc56 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -7,6 +7,7 @@ describe Banzai::Filter::RelativeLinkFilter do
contexts.reverse_merge!({
commit: commit,
project: project,
+ current_user: user,
group: group,
project_wiki: project_wiki,
ref: ref,
@@ -33,7 +34,8 @@ describe Banzai::Filter::RelativeLinkFilter do
%(<div>#{element}</div>)
end
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :public) }
+ let(:user) { create(:user) }
let(:group) { nil }
let(:project_path) { project.full_path }
let(:ref) { 'markdown' }
@@ -75,6 +77,11 @@ describe Banzai::Filter::RelativeLinkFilter do
include_examples :preserve_unchanged
end
+ context 'without project repository access' do
+ let(:project) { create(:project, :repository, repository_access_level: ProjectFeature::PRIVATE) }
+ include_examples :preserve_unchanged
+ end
+
it 'does not raise an exception on invalid URIs' do
act = link("://foo")
expect { filter(act) }.not_to raise_error
@@ -282,6 +289,37 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:relative_path) { "/#{project.full_path}#{upload_path}" }
context 'to a project upload' do
+ context 'without project repository access' do
+ let(:project) { create(:project, :repository, repository_access_level: ProjectFeature::PRIVATE) }
+
+ it 'does not rebuild relative URL for a link' do
+ doc = filter(link(upload_path))
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+
+ doc = filter(nested(link(upload_path)))
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+
+ it 'does not rebuild relative URL for an image' do
+ doc = filter(image(upload_path))
+ expect(doc.at_css('img')['src']).to eq(upload_path)
+
+ doc = filter(nested(image(upload_path)))
+ expect(doc.at_css('img')['src']).to eq(upload_path)
+ end
+
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'does not rewrite the link' do
+ doc = filter(link(upload_path))
+
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+ end
+ end
+
context 'with an absolute URL' do
let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
let(:only_path) { false }
@@ -331,11 +369,41 @@ describe Banzai::Filter::RelativeLinkFilter do
end
context 'to a group upload' do
- let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
+ let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
+ let(:upload_link) { link(upload_path) }
let(:group) { create(:group) }
let(:project) { nil }
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
+ context 'without group read access' do
+ let(:group) { create(:group, :private) }
+
+ it 'does not rewrite the link' do
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+
+ it 'does not rewrite the link for subgroup' do
+ group.update!(parent: create(:group))
+
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'does not rewrite the link' do
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+ end
+ end
+
context 'with an absolute URL' do
let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
let(:only_path) { false }
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index 483e806624c..cd932f502f3 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -49,4 +49,26 @@ describe Banzai::Filter::VideoLinkFilter do
expect(element['src']).to eq '/path/my_image.jpg'
end
end
+
+ context 'when asset proxy is enabled' do
+ it 'uses the correct src' do
+ stub_asset_proxy_setting(enabled: true)
+
+ proxy_src = 'https://assets.example.com/6d8b63'
+ canonical_src = 'http://example.com/test.mp4'
+ image = %(<img src="#{proxy_src}" data-canonical-src="#{canonical_src}" />)
+ container = filter(image, asset_proxy_enabled: true).children.first
+
+ expect(container['class']).to eq 'video-container'
+
+ video, paragraph = container.children
+
+ expect(video['src']).to eq proxy_src
+ expect(video['data-canonical-src']).to eq canonical_src
+
+ link = paragraph.children.first
+
+ expect(link['href']).to eq proxy_src
+ end
+ end
end
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 0a3e0962452..3a9ecd2fb81 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -142,4 +142,48 @@ describe Banzai::Pipeline::GfmPipeline do
expect(output).to include(Gitlab::Routing.url_helpers.milestone_path(milestone))
end
end
+
+ describe 'asset proxy' do
+ let(:project) { create(:project, :public) }
+ let(:image) { '![proxy](http://example.com/test.png)' }
+ let(:proxy) { 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67' }
+ let(:version) { Gitlab::CurrentSettings.current_application_settings.local_markdown_version }
+
+ before do
+ stub_asset_proxy_setting(enabled: true)
+ stub_asset_proxy_setting(secret_key: 'shared-secret')
+ stub_asset_proxy_setting(url: 'https://assets.example.com')
+ stub_asset_proxy_setting(whitelist: %W(gitlab.com *.mydomain.com #{Gitlab.config.gitlab.host}))
+ stub_asset_proxy_setting(domain_regexp: Banzai::Filter::AssetProxyFilter.compile_whitelist(Gitlab.config.asset_proxy.whitelist))
+ end
+
+ it 'replaces a lazy loaded img src' do
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('img').first
+
+ expect(result['data-src']).to eq(proxy)
+ end
+
+ it 'autolinks images to the proxy' do
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('a').first
+
+ expect(result['href']).to eq(proxy)
+ expect(result['data-canonical-src']).to eq('http://example.com/test.png')
+ end
+
+ it 'properly adds tooltips to link for IDN images' do
+ image = '![proxy](http://exa😄mple.com/test.png)'
+ proxy = 'https://assets.example.com/6d8b634c412a23c6bfe1b2963f174febf5635ddd/687474703a2f2f6578612546302539462539382538346d706c652e636f6d2f746573742e706e67'
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('a').first
+
+ expect(result['href']).to eq(proxy)
+ expect(result['data-canonical-src']).to eq('http://exa%F0%9F%98%84mple.com/test.png')
+ expect(result['title']).to eq 'http://xn--example-6p25f.com/test.png'
+ end
+ end
end
diff --git a/spec/lib/gitlab/anonymous_session_spec.rb b/spec/lib/gitlab/anonymous_session_spec.rb
new file mode 100644
index 00000000000..628aae81ada
--- /dev/null
+++ b/spec/lib/gitlab/anonymous_session_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
+ let(:default_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' }
+ let(:additional_session_id) { '7919a6f1bb119dd7396fadc38fd18d0d' }
+
+ subject { new_anonymous_session }
+
+ def new_anonymous_session(session_id = default_session_id)
+ described_class.new('127.0.0.1', session_id: session_id)
+ end
+
+ describe '#store_session_id_per_ip' do
+ it 'adds session id to proper key' do
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
+ end
+ end
+
+ it 'adds expiration time to key' do
+ Timecop.freeze do
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.ttl("session:lookup:ip:gitlab:127.0.0.1")).to eq(24.hours.to_i)
+ end
+ end
+ end
+
+ it 'adds id only once' do
+ subject.store_session_id_per_ip
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
+ end
+ end
+
+ context 'when there is already one session' do
+ it 'adds session id to proper key' do
+ subject.store_session_id_per_ip
+ new_anonymous_session(additional_session_id).store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to contain_exactly(default_session_id, additional_session_id)
+ end
+ end
+ end
+ end
+
+ describe '#stored_sessions' do
+ it 'returns all anonymous sessions per ip' do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
+ end
+
+ expect(subject.stored_sessions).to eq(2)
+ end
+ end
+
+ it 'removes obsolete lookup through ip entries' do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
+ end
+
+ subject.cleanup_session_per_ip_entries
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [additional_session_id]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb
index 42bc509eeef..adf36cf1050 100644
--- a/spec/lib/gitlab/authorized_keys_spec.rb
+++ b/spec/lib/gitlab/authorized_keys_spec.rb
@@ -5,10 +5,81 @@ require 'spec_helper'
describe Gitlab::AuthorizedKeys do
let(:logger) { double('logger').as_null_object }
- subject { described_class.new(logger) }
+ subject(:authorized_keys) { described_class.new(logger) }
+
+ describe '#accessible?' do
+ subject { authorized_keys.accessible? }
+
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ context 'can open file' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'cannot open file' do
+ before do
+ allow(File).to receive(:open).and_raise(Errno::EACCES)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#create' do
+ subject { authorized_keys.create }
+
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'authorized_keys file does not exist' do
+ after do
+ delete_authorized_keys_file
+ end
+
+ it 'creates authorized_keys file' do
+ expect(subject).to be_truthy
+ expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
+ end
+ end
+
+ context 'cannot create file' do
+ before do
+ allow(File).to receive(:open).and_raise(Errno::EACCES)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
describe '#add_key' do
+ let(:id) { 'key-741' }
+
+ subject { authorized_keys.add_key(id, key) }
+
context 'authorized_keys file exists' do
+ let(:key) { 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage' }
+
before do
create_authorized_keys_fixture
end
@@ -21,19 +92,20 @@ describe Gitlab::AuthorizedKeys do
auth_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E"
expect(logger).to receive(:info).with('Adding key (key-741): ssh-rsa AAAAB3NzaDAxx2E')
- expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage'))
- .to be_truthy
+ expect(subject).to be_truthy
expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line}\n")
end
end
context 'authorized_keys file does not exist' do
+ let(:key) { 'ssh-rsa AAAAB3NzaDAxx2E' }
+
before do
delete_authorized_keys_file
end
it 'creates the file' do
- expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E')).to be_truthy
+ expect(subject).to be_truthy
expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
end
end
@@ -47,6 +119,8 @@ describe Gitlab::AuthorizedKeys do
]
end
+ subject { authorized_keys.batch_add_keys(keys) }
+
context 'authorized_keys file exists' do
before do
create_authorized_keys_fixture
@@ -62,7 +136,7 @@ describe Gitlab::AuthorizedKeys do
expect(logger).to receive(:info).with('Adding key (key-12): ssh-dsa ASDFASGADG')
expect(logger).to receive(:info).with('Adding key (key-123): ssh-rsa GFDGDFSGSDFG')
- expect(subject.batch_add_keys(keys)).to be_truthy
+ expect(subject).to be_truthy
expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line1}\n#{auth_line2}\n")
end
@@ -70,7 +144,7 @@ describe Gitlab::AuthorizedKeys do
let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] }
it "doesn't add keys" do
- expect(subject.batch_add_keys(keys)).to be_falsey
+ expect(subject).to be_falsey
expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n")
end
end
@@ -82,16 +156,28 @@ describe Gitlab::AuthorizedKeys do
end
it 'creates the file' do
- expect(subject.batch_add_keys(keys)).to be_truthy
+ expect(subject).to be_truthy
expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
end
end
end
describe '#rm_key' do
+ let(:key) { 'key-741' }
+
+ subject { authorized_keys.rm_key(key) }
+
context 'authorized_keys file exists' do
+ let(:other_line) { "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" }
+ let(:delete_line) { "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E" }
+
before do
create_authorized_keys_fixture
+
+ File.open(tmp_authorized_keys_path, 'a') do |auth_file|
+ auth_file.puts delete_line
+ auth_file.puts other_line
+ end
end
after do
@@ -99,16 +185,10 @@ describe Gitlab::AuthorizedKeys do
end
it "removes the right line" do
- other_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E"
- delete_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E"
erased_line = delete_line.gsub(/./, '#')
- File.open(tmp_authorized_keys_path, 'a') do |auth_file|
- auth_file.puts delete_line
- auth_file.puts other_line
- end
expect(logger).to receive(:info).with('Removing key (key-741)')
- expect(subject.rm_key('key-741')).to be_truthy
+ expect(subject).to be_truthy
expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{erased_line}\n#{other_line}\n")
end
end
@@ -118,13 +198,13 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it 'returns false' do
- expect(subject.rm_key('key-741')).to be_falsey
- end
+ it { is_expected.to be_falsey }
end
end
describe '#clear' do
+ subject { authorized_keys.clear }
+
context 'authorized_keys file exists' do
before do
create_authorized_keys_fixture
@@ -134,9 +214,7 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it "returns true" do
- expect(subject.clear).to be_truthy
- end
+ it { is_expected.to be_truthy }
end
context 'authorized_keys file does not exist' do
@@ -144,13 +222,13 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it "still returns true" do
- expect(subject.clear).to be_truthy
- end
+ it { is_expected.to be_truthy }
end
end
describe '#list_key_ids' do
+ subject { authorized_keys.list_key_ids }
+
context 'authorized_keys file exists' do
before do
create_authorized_keys_fixture(
@@ -163,9 +241,7 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it 'returns array of key IDs' do
- expect(subject.list_key_ids).to eq([1, 2, 3, 9000])
- end
+ it { is_expected.to eq([1, 2, 3, 9000]) }
end
context 'authorized_keys file does not exist' do
@@ -173,9 +249,7 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it 'returns an empty array' do
- expect(subject.list_key_ids).to be_empty
- end
+ it { is_expected.to be_empty }
end
end
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index b25f0f6b887..b3dedfe1f77 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -130,7 +130,7 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
end
it 'returns error when saving project ID fails' do
- allow(application_setting).to receive(:update) { false }
+ allow(application_setting).to receive(:save) { false }
expect { result }.to raise_error(StandardError, 'Could not save project ID')
end
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
index 4d2f08f95fc..790b0428d19 100644
--- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -102,6 +102,23 @@ describe Gitlab::Gfm::ReferenceRewriter do
end
end
+ context 'with a commit' do
+ let(:old_project) { create(:project, :repository, name: 'old-project', group: group) }
+ let(:commit) { old_project.commit }
+
+ context 'reference to an absolute URL to a commit' do
+ let(:text) { Gitlab::UrlBuilder.build(commit) }
+
+ it { is_expected.to eq(text) }
+ end
+
+ context 'reference to a commit' do
+ let(:text) { commit.id }
+
+ it { is_expected.to eq("#{old_project_ref}@#{text}") }
+ end
+ end
+
context 'reference contains project milestone' do
let!(:milestone) do
create(:milestone, title: '9.0', project: old_project)
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 3c6b17c10ec..ec4a6ef05b9 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -483,3 +483,4 @@ lists:
- milestone
- board
- label
+- list_user_preferences
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index d6e1fbaa979..0aef4887c75 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -396,6 +396,27 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(project.lfs_enabled).to be_falsey
end
+
+ it 'overrides project feature access levels' do
+ access_level_keys = project.project_feature.attributes.keys.select { |a| a =~ /_access_level/ }
+
+ # `pages_access_level` is not included, since it is not available in the public API
+ # and has a dependency on project's visibility level
+ # see ProjectFeature model
+ access_level_keys.delete('pages_access_level')
+
+ disabled_access_levels = Hash[access_level_keys.collect { |item| [item, 'disabled'] }]
+
+ project.create_import_data(data: { override_params: disabled_access_levels })
+
+ restored_project_json
+
+ aggregate_failures do
+ access_level_keys.each do |key|
+ expect(project.public_send(key)).to eq(ProjectFeature::DISABLED)
+ end
+ end
+ end
end
context 'with a project that has a group' do
diff --git a/spec/lib/gitlab/internal_post_receive/response_spec.rb b/spec/lib/gitlab/internal_post_receive/response_spec.rb
new file mode 100644
index 00000000000..f43762c9248
--- /dev/null
+++ b/spec/lib/gitlab/internal_post_receive/response_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::InternalPostReceive::Response do
+ subject { described_class.new }
+
+ describe '#add_merge_request_urls' do
+ context 'when there are urls_data' do
+ it 'adds a message for each merge request URL' do
+ urls_data = [
+ { new_merge_request: false, branch_name: 'foo', url: 'http://example.com/foo/bar/merge_requests/1' },
+ { new_merge_request: true, branch_name: 'bar', url: 'http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar' }
+ ]
+
+ subject.add_merge_request_urls(urls_data)
+
+ expected = [a_kind_of(described_class::Message), a_kind_of(described_class::Message)]
+ expect(subject.messages).to match(expected)
+ end
+ end
+ end
+
+ describe '#add_merge_request_url' do
+ context 'when :new_merge_request is false' do
+ it 'adds a basic message to view the existing merge request' do
+ url_data = { new_merge_request: false, branch_name: 'foo', url: 'http://example.com/foo/bar/merge_requests/1' }
+
+ subject.add_merge_request_url(url_data)
+
+ message = <<~MESSAGE.strip
+ View merge request for foo:
+ http://example.com/foo/bar/merge_requests/1
+ MESSAGE
+
+ expect(subject.messages.first.message).to eq(message)
+ expect(subject.messages.first.type).to eq(:basic)
+ end
+ end
+
+ context 'when :new_merge_request is true' do
+ it 'adds a basic message to create a new merge request' do
+ url_data = { new_merge_request: true, branch_name: 'bar', url: 'http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar' }
+
+ subject.add_merge_request_url(url_data)
+
+ message = <<~MESSAGE.strip
+ To create a merge request for bar, visit:
+ http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar
+ MESSAGE
+
+ expect(subject.messages.first.message).to eq(message)
+ expect(subject.messages.first.type).to eq(:basic)
+ end
+ end
+ end
+
+ describe '#add_basic_message' do
+ context 'when text is present' do
+ it 'adds a basic message' do
+ subject.add_basic_message('hello')
+
+ expect(subject.messages.first.message).to eq('hello')
+ expect(subject.messages.first.type).to eq(:basic)
+ end
+ end
+
+ context 'when text is blank' do
+ it 'does not add a message' do
+ subject.add_basic_message(' ')
+
+ expect(subject.messages).to be_blank
+ end
+ end
+ end
+
+ describe '#add_alert_message' do
+ context 'when text is present' do
+ it 'adds a alert message' do
+ subject.add_alert_message('hello')
+
+ expect(subject.messages.first.message).to eq('hello')
+ expect(subject.messages.first.type).to eq(:alert)
+ end
+ end
+
+ context 'when text is blank' do
+ it 'does not add a message' do
+ subject.add_alert_message(' ')
+
+ expect(subject.messages).to be_blank
+ end
+ end
+ end
+
+ describe '#reference_counter_decreased' do
+ context 'initially' do
+ it 'reference_counter_decreased is set to false' do
+ expect(subject.reference_counter_decreased).to eq(false)
+ end
+ end
+ end
+
+ describe '#reference_counter_decreased=' do
+ context 'when the argument is truthy' do
+ it 'reference_counter_decreased is truthy' do
+ subject.reference_counter_decreased = true
+
+ expect(subject.reference_counter_decreased).to be_truthy
+ end
+ end
+
+ context 'when the argument is falsey' do
+ it 'reference_counter_decreased is falsey' do
+ subject.reference_counter_decreased = false
+
+ expect(subject.reference_counter_decreased).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index f49d4e23e39..e5d688aa391 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
describe Gitlab::Kubernetes::KubeClient do
+ include StubRequests
include KubernetesHelpers
let(:api_url) { 'https://kubernetes.example.com/prefix' }
@@ -14,6 +15,17 @@ describe Gitlab::Kubernetes::KubeClient do
stub_kubeclient_discover(api_url)
end
+ def method_call(client, method_name)
+ case method_name
+ when /\A(get_|delete_)/
+ client.public_send(method_name)
+ when /\A(create_|update_)/
+ client.public_send(method_name, {})
+ else
+ raise "Unknown method name #{method_name}"
+ end
+ end
+
shared_examples 'a Kubeclient' do
it 'is a Kubeclient::Client' do
is_expected.to be_an_instance_of Kubeclient::Client
@@ -25,28 +37,30 @@ describe Gitlab::Kubernetes::KubeClient do
end
shared_examples 'redirection not allowed' do |method_name|
- before do
- redirect_url = 'https://not-under-our-control.example.com/api/v1/pods'
+ context 'api_url is redirected' do
+ before do
+ redirect_url = 'https://not-under-our-control.example.com/api/v1/pods'
- stub_request(:get, %r{\A#{api_url}/})
- .to_return(status: 302, headers: { location: redirect_url })
+ stub_request(:get, %r{\A#{api_url}/})
+ .to_return(status: 302, headers: { location: redirect_url })
- stub_request(:get, redirect_url)
- .to_return(status: 200, body: '{}')
- end
+ stub_request(:get, redirect_url)
+ .to_return(status: 200, body: '{}')
+ end
- it 'does not follow redirects' do
- method_call = -> do
- case method_name
- when /\A(get_|delete_)/
- client.public_send(method_name)
- when /\A(create_|update_)/
- client.public_send(method_name, {})
- else
- raise "Unknown method name #{method_name}"
- end
+ it 'does not follow redirects' do
+ expect { method_call(client, method_name) }.to raise_error(Kubeclient::HttpError)
end
- expect { method_call.call }.to raise_error(Kubeclient::HttpError)
+ end
+ end
+
+ shared_examples 'dns rebinding not allowed' do |method_name|
+ it 'does not allow DNS rebinding' do
+ stub_dns(api_url, ip_address: '8.8.8.8')
+ client
+
+ stub_dns(api_url, ip_address: '192.168.2.120')
+ expect { method_call(client, method_name) }.to raise_error(ArgumentError, /is blocked/)
end
end
@@ -160,6 +174,7 @@ describe Gitlab::Kubernetes::KubeClient do
].each do |method|
describe "##{method}" do
include_examples 'redirection not allowed', method
+ include_examples 'dns rebinding not allowed', method
it 'delegates to the core client' do
expect(client).to delegate_method(method).to(:core_client)
@@ -185,6 +200,7 @@ describe Gitlab::Kubernetes::KubeClient do
].each do |method|
describe "##{method}" do
include_examples 'redirection not allowed', method
+ include_examples 'dns rebinding not allowed', method
it 'delegates to the rbac client' do
expect(client).to delegate_method(method).to(:rbac_client)
@@ -203,6 +219,7 @@ describe Gitlab::Kubernetes::KubeClient do
describe '#get_deployments' do
include_examples 'redirection not allowed', 'get_deployments'
+ include_examples 'dns rebinding not allowed', 'get_deployments'
it 'delegates to the extensions client' do
expect(client).to delegate_method(:get_deployments).to(:extensions_client)
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index f52095bf633..16595102375 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -202,7 +202,7 @@ describe Gitlab::Middleware::Go do
def expect_response_with_path(response, protocol, path)
repository_url = case protocol
when :ssh
- "ssh://git@#{Gitlab.config.gitlab.host}/#{path}.git"
+ "ssh://#{Gitlab.config.gitlab.user}@#{Gitlab.config.gitlab.host}/#{path}.git"
when :http, nil
"http://#{Gitlab.config.gitlab.host}/#{path}.git"
end
diff --git a/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
new file mode 100644
index 00000000000..3b92261f0fe
--- /dev/null
+++ b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::PerformanceBar::WithTopLevelWarnings do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { Module.new }
+
+ before do
+ subject.singleton_class.prepend(described_class)
+ end
+
+ describe '#has_warnings?' do
+ where(:has_warnings, :results) do
+ false | { data: {} }
+ false | { data: { gitaly: { warnings: [] } } }
+ true | { data: { gitaly: { warnings: [1] } } }
+ true | { data: { gitaly: { warnings: [] }, redis: { warnings: [1] } } }
+ end
+
+ with_them do
+ it do
+ expect(subject.has_warnings?(results)).to eq(has_warnings)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index fd1338b55a6..808eb865a21 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -6,7 +6,6 @@ describe Gitlab::RepositoryCacheAdapter do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:cache) { repository.send(:cache) }
- let(:redis_set_cache) { repository.send(:redis_set_cache) }
describe '#cache_method_output', :use_clean_rails_memory_store_caching do
let(:fallback) { 10 }
@@ -209,11 +208,9 @@ describe Gitlab::RepositoryCacheAdapter do
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:rendered_readme)
- expect(cache).to receive(:expire).with(:branch_names)
- expect(redis_set_cache).to receive(:expire).with(:rendered_readme)
- expect(redis_set_cache).to receive(:expire).with(:branch_names)
+ expect(cache).to receive(:expire).with(:gitignore)
- repository.expire_method_caches(%i(rendered_readme branch_names))
+ repository.expire_method_caches(%i(rendered_readme gitignore))
end
it 'does not expire caches for non-existent methods' do
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
deleted file mode 100644
index 87e51f801e5..00000000000
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
- let(:project) { create(:project) }
- let(:repository) { project.repository }
- let(:namespace) { "#{repository.full_path}:#{project.id}" }
- let(:cache) { described_class.new(repository) }
-
- describe '#cache_key' do
- subject { cache.cache_key(:foo) }
-
- it 'includes the namespace' do
- is_expected.to eq("foo:#{namespace}:set")
- end
-
- context 'with a given namespace' do
- let(:extra_namespace) { 'my:data' }
- let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) }
-
- it 'includes the full namespace' do
- is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set")
- end
- end
- end
-
- describe '#expire' do
- it 'expires the given key from the cache' do
- cache.write(:foo, ['value'])
-
- expect(cache.read(:foo)).to contain_exactly('value')
- expect(cache.expire(:foo)).to eq(1)
- expect(cache.read(:foo)).to be_empty
- end
- end
-
- describe '#exist?' do
- it 'checks whether the key exists' do
- expect(cache.exist?(:foo)).to be(false)
-
- cache.write(:foo, ['value'])
-
- expect(cache.exist?(:foo)).to be(true)
- end
- end
-
- describe '#fetch' do
- let(:blk) { -> { ['block value'] } }
-
- subject { cache.fetch(:foo, &blk) }
-
- it 'fetches the key from the cache when filled' do
- cache.write(:foo, ['value'])
-
- is_expected.to contain_exactly('value')
- end
-
- it 'writes the value of the provided block when empty' do
- cache.expire(:foo)
-
- is_expected.to contain_exactly('block value')
- expect(cache.read(:foo)).to contain_exactly('block value')
- end
- end
-
- describe '#include?' do
- it 'checks inclusion in the Redis set' do
- cache.write(:foo, ['value'])
-
- expect(cache.include?(:foo, 'value')).to be(true)
- expect(cache.include?(:foo, 'bar')).to be(false)
- end
- end
-end
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index 22c5f27dc6d..f882dbbdb5c 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -7,7 +7,9 @@ describe Gitlab::Sanitizers::Exif do
describe '#batch_clean' do
context 'with image uploads' do
- let!(:uploads) { create_list(:upload, 3, :with_file, :issuable_upload) }
+ set(:upload1) { create(:upload, :with_file, :issuable_upload) }
+ set(:upload2) { create(:upload, :with_file, :personal_snippet_upload) }
+ set(:upload3) { create(:upload, :with_file, created_at: 3.days.ago) }
it 'processes all uploads if range ID is not set' do
expect(sanitizer).to receive(:clean).exactly(3).times
@@ -18,7 +20,19 @@ describe Gitlab::Sanitizers::Exif do
it 'processes only uploads in the selected range' do
expect(sanitizer).to receive(:clean).once
- sanitizer.batch_clean(start_id: uploads[1].id, stop_id: uploads[1].id)
+ sanitizer.batch_clean(start_id: upload1.id, stop_id: upload1.id)
+ end
+
+ it 'processes only uploads for the selected uploader' do
+ expect(sanitizer).to receive(:clean).once
+
+ sanitizer.batch_clean(uploader: 'PersonalFileUploader')
+ end
+
+ it 'processes only uploads created since specified date' do
+ expect(sanitizer).to receive(:clean).exactly(2).times
+
+ sanitizer.batch_clean(since: 2.days.ago)
end
it 'pauses if sleep_time is set' do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 0ba16b93ee7..fe4853fd819 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -52,38 +52,14 @@ describe Gitlab::Shell do
describe '#add_key' do
context 'when authorized_keys_enabled is true' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with add-key command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([
- :gitlab_shell_keys_path,
- 'add-key',
- 'key-123',
- 'ssh-rsa foobar'
- ])
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys)
- .to receive(:add_key)
- .with('key-123', 'ssh-rsa foobar')
+ expect(gitlab_authorized_keys)
+ .to receive(:add_key)
+ .with('key-123', 'ssh-rsa foobar')
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
- end
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
end
end
@@ -92,24 +68,10 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
- context 'authorized_keys_file set' do
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
end
end
@@ -118,38 +80,14 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with add-key command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([
- :gitlab_shell_keys_path,
- 'add-key',
- 'key-123',
- 'ssh-rsa foobar'
- ])
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys)
- .to receive(:add_key)
- .with('key-123', 'ssh-rsa foobar')
+ expect(gitlab_authorized_keys)
+ .to receive(:add_key)
+ .with('key-123', 'ssh-rsa foobar')
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
- end
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
end
end
end
@@ -158,50 +96,14 @@ describe Gitlab::Shell do
let(:keys) { [double(shell_id: 'key-123', key: 'ssh-rsa foobar')] }
context 'when authorized_keys_enabled is true' do
- context 'authorized_keys_file not set' do
- let(:io) { double }
-
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
-
- context 'valid keys' do
- before do
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls gitlab-keys with batch-add-keys command' do
- expect(IO)
- .to receive(:popen)
- .with("gitlab_shell_keys_path batch-add-keys", 'w')
- .and_yield(io)
-
- expect(io).to receive(:puts).with("key-123\tssh-rsa foobar")
- expect(gitlab_shell.batch_add_keys(keys)).to be_truthy
- end
- end
-
- context 'invalid keys' do
- let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] }
-
- it 'catches failure and returns false' do
- expect(gitlab_shell.batch_add_keys(keys)).to be_falsey
- end
- end
- end
+ it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys)
+ .to receive(:batch_add_keys)
+ .with(keys)
- expect(gitlab_authorized_keys)
- .to receive(:batch_add_keys)
- .with(keys)
-
- gitlab_shell.batch_add_keys(keys)
- end
+ gitlab_shell.batch_add_keys(keys)
end
end
@@ -210,24 +112,10 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
-
- it 'does nothing' do
- expect(IO).not_to receive(:popen)
-
- gitlab_shell.batch_add_keys(keys)
- end
- end
-
- context 'authorized_keys_file set' do
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.batch_add_keys(keys)
- end
+ gitlab_shell.batch_add_keys(keys)
end
end
@@ -236,72 +124,25 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- context 'authorized_keys_file not set' do
- let(:io) { double }
+ it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls gitlab-keys with batch-add-keys command' do
- expect(IO)
- .to receive(:popen)
- .with("gitlab_shell_keys_path batch-add-keys", 'w')
- .and_yield(io)
+ expect(gitlab_authorized_keys)
+ .to receive(:batch_add_keys)
+ .with(keys)
- expect(io).to receive(:puts).with("key-123\tssh-rsa foobar")
-
- gitlab_shell.batch_add_keys(keys)
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
-
- expect(gitlab_authorized_keys)
- .to receive(:batch_add_keys)
- .with(keys)
-
- gitlab_shell.batch_add_keys(keys)
- end
+ gitlab_shell.batch_add_keys(keys)
end
end
end
describe '#remove_key' do
context 'when authorized_keys_enabled is true' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with rm-key command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([
- :gitlab_shell_keys_path,
- 'rm-key',
- 'key-123'
- ])
-
- gitlab_shell.remove_key('key-123')
- end
- end
+ it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
- context 'authorized_keys_file not set' do
- it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
-
- gitlab_shell.remove_key('key-123')
- end
+ gitlab_shell.remove_key('key-123')
end
end
@@ -310,24 +151,10 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
-
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.remove_key('key-123')
- end
- end
-
- context 'authorized_keys_file set' do
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.remove_key('key-123')
- end
+ gitlab_shell.remove_key('key-123')
end
end
@@ -336,64 +163,22 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with rm-key command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([
- :gitlab_shell_keys_path,
- 'rm-key',
- 'key-123'
- ])
-
- gitlab_shell.remove_key('key-123')
- end
- end
-
- context 'authorized_keys_file not set' do
- it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
+ it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
- gitlab_shell.remove_key('key-123')
- end
+ gitlab_shell.remove_key('key-123')
end
end
end
describe '#remove_all_keys' do
context 'when authorized_keys_enabled is true' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
+ it 'calls Gitlab::AuthorizedKeys#clear' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:clear)
- it 'calls #gitlab_shell_fast_execute with clear command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([:gitlab_shell_keys_path, 'clear'])
-
- gitlab_shell.remove_all_keys
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#clear' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:clear)
-
- gitlab_shell.remove_all_keys
- end
+ gitlab_shell.remove_all_keys
end
end
@@ -402,24 +187,10 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
-
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.remove_all_keys
- end
- end
-
- context 'authorized_keys_file set' do
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.remove_all_keys
- end
+ gitlab_shell.remove_all_keys
end
end
@@ -428,163 +199,73 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with clear command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([:gitlab_shell_keys_path, 'clear'])
+ it 'calls Gitlab::AuthorizedKeys#clear' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:clear)
- gitlab_shell.remove_all_keys
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#clear' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:clear)
-
- gitlab_shell.remove_all_keys
- end
+ gitlab_shell.remove_all_keys
end
end
end
describe '#remove_keys_not_found_in_db' do
context 'when keys are in the file that are not in the DB' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
- @another_key = create(:key) # this one IS in the DB
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- expect(gitlab_shell).to receive(:remove_key).with('key-9876')
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
-
- gitlab_shell.remove_keys_not_found_in_db
- end
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
end
- context 'authorized_keys_file set' do
- before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
- @another_key = create(:key) # this one IS in the DB
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- expect(gitlab_shell).to receive(:remove_key).with('key-9876')
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ expect(gitlab_shell).to receive(:remove_key).with('key-9876')
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
- gitlab_shell.remove_keys_not_found_in_db
- end
+ gitlab_shell.remove_keys_not_found_in_db
end
end
context 'when keys there are duplicate keys in the file that are not in the DB' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
-
- gitlab_shell.remove_keys_not_found_in_db
- end
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
end
- context 'authorized_keys_file set' do
- before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- gitlab_shell.remove_keys_not_found_in_db
- end
+ gitlab_shell.remove_keys_not_found_in_db
end
end
context 'when keys there are duplicate keys in the file that ARE in the DB' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- gitlab_shell.remove_all_keys
- @key = create(:key)
- gitlab_shell.add_key(@key.shell_id, @key.key)
- end
-
- it 'does not remove the key' do
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
-
- gitlab_shell.remove_keys_not_found_in_db
- end
+ before do
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
end
- context 'authorized_keys_file set' do
- before do
- gitlab_shell.remove_all_keys
- @key = create(:key)
- gitlab_shell.add_key(@key.shell_id, @key.key)
- end
-
- it 'does not remove the key' do
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
+ it 'does not remove the key' do
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
- gitlab_shell.remove_keys_not_found_in_db
- end
+ gitlab_shell.remove_keys_not_found_in_db
end
end
unless ENV['CI'] # Skip in CI, it takes 1 minute
context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- gitlab_shell.remove_all_keys
- 100.times { |i| create(:key) } # first batch is all in the DB
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys not in the DB' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
-
- gitlab_shell.remove_keys_not_found_in_db
- end
+ before do
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
end
- context 'authorized_keys_file set' do
- before do
- gitlab_shell.remove_all_keys
- 100.times { |i| create(:key) } # first batch is all in the DB
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys not in the DB' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ it 'removes the keys not in the DB' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- gitlab_shell.remove_keys_not_found_in_db
- end
+ gitlab_shell.remove_keys_not_found_in_db
end
end
end
diff --git a/spec/lib/gitlab/slash_commands/issue_close_spec.rb b/spec/lib/gitlab/slash_commands/issue_close_spec.rb
new file mode 100644
index 00000000000..c0760ce0ba6
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/issue_close_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::IssueClose do
+ describe '#execute' do
+ let(:issue) { create(:issue, project: project) }
+ let(:project) { create(:project) }
+ let(:user) { issue.author }
+ let(:chat_name) { double(:chat_name, user: user) }
+ let(:regex_match) { described_class.match("issue close #{issue.iid}") }
+
+ subject do
+ described_class.new(project, chat_name).execute(regex_match)
+ end
+
+ context 'when the user does not have permission' do
+ let(:chat_name) { double(:chat_name, user: create(:user)) }
+
+ it 'does not allow the user to close the issue' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("not found")
+ expect(issue.reload).to be_open
+ end
+ end
+
+ context 'the issue exists' do
+ let(:title) { subject[:attachments].first[:title] }
+
+ it 'closes and returns the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(issue.reload).to be_closed
+ expect(title).to start_with(issue.title)
+ end
+
+ context 'when its reference is given' do
+ let(:regex_match) { described_class.match("issue close #{issue.to_reference}") }
+
+ it 'closes and returns the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(issue.reload).to be_closed
+ expect(title).to start_with(issue.title)
+ end
+ end
+ end
+
+ context 'the issue does not exist' do
+ let(:regex_match) { described_class.match("issue close 2343242") }
+
+ it "returns not found" do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("not found")
+ end
+ end
+
+ context 'when the issue is already closed' do
+ let(:issue) { create(:issue, :closed, project: project) }
+
+ it 'shows the issue' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(issue.reload).to be_closed
+ expect(subject[:text]).to match("already closed")
+ end
+ end
+ end
+
+ describe '.match' do
+ it 'matches the iid' do
+ match = described_class.match("issue close 123")
+
+ expect(match[:iid]).to eq("123")
+ end
+
+ it 'accepts a reference' do
+ match = described_class.match("issue close #{Issue.reference_prefix}123")
+
+ expect(match[:iid]).to eq("123")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
new file mode 100644
index 00000000000..adc13b4ee56
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Presenters::IssueClose do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:attachment) { subject[:attachments].first }
+
+ subject { described_class.new(issue).present }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject).to have_key(:attachments)
+ expect(attachment[:title]).to start_with(issue.title)
+ end
+
+ context 'confidential issue' do
+ let(:issue) { create(:issue, :confidential, project: project) }
+
+ it 'shows an ephemeral response' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/snowplow_tracker_spec.rb b/spec/lib/gitlab/snowplow_tracker_spec.rb
deleted file mode 100644
index 073a33e5973..00000000000
--- a/spec/lib/gitlab/snowplow_tracker_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-require 'spec_helper'
-
-describe Gitlab::SnowplowTracker do
- let(:timestamp) { Time.utc(2017, 3, 22) }
-
- around do |example|
- Timecop.freeze(timestamp) { example.run }
- end
-
- subject { described_class.track_event('epics', 'action', property: 'what', value: 'doit') }
-
- context '.track_event' do
- context 'when Snowplow tracker is disabled' do
- it 'does not track the event' do
- expect(SnowplowTracker::Tracker).not_to receive(:new)
-
- subject
- end
- end
-
- context 'when Snowplow tracker is enabled' do
- before do
- stub_application_setting(snowplow_enabled: true)
- stub_application_setting(snowplow_site_id: 'awesome gitlab')
- stub_application_setting(snowplow_collector_hostname: 'url.com')
- end
-
- it 'tracks the event' do
- tracker = double
-
- expect(::SnowplowTracker::Tracker).to receive(:new)
- .with(
- an_instance_of(::SnowplowTracker::Emitter),
- an_instance_of(::SnowplowTracker::Subject),
- 'cf', 'awesome gitlab'
- ).and_return(tracker)
- expect(tracker).to receive(:track_struct_event)
- .with('epics', 'action', nil, 'what', 'doit', nil, timestamp.to_i)
-
- subject
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
new file mode 100644
index 00000000000..f14e74427e1
--- /dev/null
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::Tracking do
+ let(:timestamp) { Time.utc(2017, 3, 22) }
+
+ before do
+ stub_application_setting(snowplow_enabled: true)
+ stub_application_setting(snowplow_collector_hostname: 'gitfoo.com')
+ stub_application_setting(snowplow_cookie_domain: '.gitfoo.com')
+ stub_application_setting(snowplow_site_id: '_abc123_')
+ end
+
+ describe '.snowplow_options' do
+ subject(&method(:described_class))
+
+ it 'returns useful client options' do
+ expect(subject.snowplow_options(nil)).to eq(
+ namespace: 'gl',
+ hostname: 'gitfoo.com',
+ cookieDomain: '.gitfoo.com',
+ appId: '_abc123_',
+ pageTrackingEnabled: true,
+ activityTrackingEnabled: true
+ )
+ end
+
+ it 'enables features using feature flags' do
+ stub_feature_flags(additional_snowplow_tracking: true)
+ allow(Feature).to receive(:enabled?).with(
+ :additional_snowplow_tracking,
+ '_group_'
+ ).and_return(false)
+
+ expect(subject.snowplow_options('_group_')).to include(
+ pageTrackingEnabled: false,
+ activityTrackingEnabled: false
+ )
+ end
+ end
+
+ describe '.event' do
+ subject(&method(:described_class))
+
+ around do |example|
+ Timecop.freeze(timestamp) { example.run }
+ end
+
+ it 'can track events' do
+ tracker = double
+
+ expect(SnowplowTracker::Emitter).to receive(:new).with(
+ 'gitfoo.com'
+ ).and_return('_emitter_')
+
+ expect(SnowplowTracker::Tracker).to receive(:new).with(
+ '_emitter_',
+ an_instance_of(SnowplowTracker::Subject),
+ 'gl',
+ '_abc123_'
+ ).and_return(tracker)
+
+ expect(tracker).to receive(:track_struct_event).with(
+ 'category',
+ 'action',
+ '_label_',
+ '_property_',
+ '_value_',
+ '_context_',
+ timestamp.to_i
+ )
+
+ subject.event('category', 'action',
+ label: '_label_',
+ property: '_property_',
+ value: '_value_',
+ context: '_context_'
+ )
+ end
+
+ it 'does not track when not enabled' do
+ stub_application_setting(snowplow_enabled: false)
+ expect(SnowplowTracker::Tracker).not_to receive(:new)
+
+ subject.event('epics', 'action', property: 'what', value: 'doit')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/visibility_level_checker_spec.rb b/spec/lib/gitlab/visibility_level_checker_spec.rb
new file mode 100644
index 00000000000..325ac3c6f31
--- /dev/null
+++ b/spec/lib/gitlab/visibility_level_checker_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Gitlab::VisibilityLevelChecker do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:visibility_level_checker) { }
+ let(:override_params) { {} }
+
+ subject { described_class.new(user, project, project_params: override_params) }
+
+ describe '#level_restricted?' do
+ context 'when visibility level is allowed' do
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+
+ context 'when visibility level is restricted' do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it 'returns true and visibility name' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(true)
+ expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ context 'overridden visibility' do
+ let(:override_params) do
+ {
+ import_data: {
+ data: {
+ override_params: {
+ visibility: override_visibility
+ }
+ }
+ }
+ }
+ end
+
+ context 'when restricted' do
+ let(:override_visibility) { 'public' }
+
+ it 'returns true and visibility name' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(true)
+ expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ context 'when misspelled' do
+ let(:override_visibility) { 'publik' }
+
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+
+ context 'when import_data is missing' do
+ let(:override_params) { {} }
+
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 5c5ff46112f..98421cd12d3 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -14,6 +14,12 @@ describe Gitlab::Workhorse do
[key, command, params]
end
+ before do
+ allow(Feature::Gitaly).to receive(:server_feature_flags).and_return({
+ 'gitaly-feature-foobar' => 'true'
+ })
+ end
+
describe ".send_git_archive" do
let(:ref) { 'master' }
let(:format) { 'zip' }
@@ -41,6 +47,7 @@ describe Gitlab::Workhorse do
expected_params = metadata.merge(
'GitalyRepository' => repository.gitaly_repository.to_h,
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
}
@@ -69,6 +76,7 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-archive')
expect(params).to eq({
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
@@ -117,6 +125,7 @@ describe Gitlab::Workhorse do
expect(command).to eq("git-format-patch")
expect(params).to eq({
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
@@ -178,6 +187,7 @@ describe Gitlab::Workhorse do
expect(command).to eq("git-diff")
expect(params).to eq({
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
@@ -315,6 +325,7 @@ describe Gitlab::Workhorse do
let(:gitaly_params) do
{
GitalyServer: {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address('default'),
token: Gitlab::GitalyClient.token('default')
}
@@ -463,6 +474,7 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-blob')
expect(params).to eq({
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
@@ -504,6 +516,7 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-snapshot')
expect(params).to eq(
'GitalyServer' => {
+ 'features' => { 'gitaly-feature-foobar' => 'true' },
'address' => Gitlab::GitalyClient.address(project.repository_storage),
'token' => Gitlab::GitalyClient.token(project.repository_storage)
},
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index cbc5649fc09..1fc363460ae 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -98,6 +98,33 @@ describe Gitlab do
end
end
+ describe '.dev_env_org_or_com?' do
+ it 'is true when on .com' do
+ allow(described_class).to receive_messages(com?: true, org?: false)
+
+ expect(described_class.dev_env_org_or_com?).to eq true
+ end
+
+ it 'is true when org' do
+ allow(described_class).to receive_messages(com?: false, org?: true)
+
+ expect(described_class.dev_env_org_or_com?).to eq true
+ end
+
+ it 'is true when dev env' do
+ allow(described_class).to receive_messages(com?: false, org?: false)
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
+
+ expect(described_class.dev_env_org_or_com?).to eq true
+ end
+
+ it 'is false when not dev, org or com' do
+ allow(described_class).to receive_messages(com?: false, org?: false)
+
+ expect(described_class.dev_env_org_or_com?).to eq false
+ end
+ end
+
describe '.ee?' do
before do
described_class.instance_variable_set(:@is_ee, nil)
diff --git a/spec/lib/peek/views/detailed_view_spec.rb b/spec/lib/peek/views/detailed_view_spec.rb
new file mode 100644
index 00000000000..d8660a55ea9
--- /dev/null
+++ b/spec/lib/peek/views/detailed_view_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Peek::Views::DetailedView, :request_store do
+ context 'when a class defines thresholds' do
+ let(:threshold_view) do
+ Class.new(described_class) do
+ def self.thresholds
+ {
+ calls: 1,
+ duration: 10,
+ individual_call: 5
+ }
+ end
+
+ def key
+ 'threshold-view'
+ end
+ end.new
+ end
+
+ context 'when the results exceed the calls threshold' do
+ before do
+ allow(threshold_view)
+ .to receive(:detail_store).and_return([{ duration: 0.001 }, { duration: 0.001 }])
+ end
+
+ it 'adds a warning to the results key' do
+ expect(threshold_view.results).to include(warnings: [a_string_matching('threshold-view calls')])
+ end
+ end
+
+ context 'when the results exceed the duration threshold' do
+ before do
+ allow(threshold_view)
+ .to receive(:detail_store).and_return([{ duration: 0.011 }])
+ end
+
+ it 'adds a warning to the results key' do
+ expect(threshold_view.results).to include(warnings: [a_string_matching('threshold-view duration')])
+ end
+ end
+
+ context 'when a single call exceeds the duration threshold' do
+ before do
+ allow(threshold_view)
+ .to receive(:detail_store).and_return([{ duration: 0.001 }, { duration: 0.006 }])
+ end
+
+ it 'adds a warning to that call detail entry' do
+ expect(threshold_view.results)
+ .to include(details: a_collection_containing_exactly(
+ { duration: 1.0, warnings: [] },
+ { duration: 6.0, warnings: ['6.0 over 5'] }
+ ))
+ end
+ end
+ end
+
+ context 'when a view does not define thresholds' do
+ let(:no_threshold_view) { Class.new(described_class).new }
+
+ before do
+ allow(no_threshold_view)
+ .to receive(:detail_store).and_return([{ duration: 100 }, { duration: 100 }])
+ end
+
+ it 'does not add warnings to the top level' do
+ expect(no_threshold_view.results).to include(warnings: [])
+ end
+
+ it 'does not add warnings to call details entries' do
+ expect(no_threshold_view.results)
+ .to include(details: a_collection_containing_exactly(
+ { duration: 100000, warnings: [] },
+ { duration: 100000, warnings: [] }
+ ))
+ end
+ end
+end
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
index 61096e6c69e..fa9532226f2 100644
--- a/spec/lib/peek/views/redis_detailed_spec.rb
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -21,10 +21,10 @@ describe Peek::Views::RedisDetailed, :request_store do
expect(subject.results[:details].count).to eq(1)
expect(subject.results[:details].first)
- .to eq({
- cmd: expected,
- duration: 1000
- })
+ .to include({
+ cmd: expected,
+ duration: 1000
+ })
end
end
diff --git a/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
new file mode 100644
index 00000000000..1a8123c3f0a
--- /dev/null
+++ b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SystemCheck::App::AuthorizedKeysPermissionCheck do
+ subject(:system_check) { described_class.new }
+
+ describe '#skip?' do
+ subject { system_check.skip? }
+
+ context 'authorized keys enabled' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'authorized keys not enabled' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
+
+ describe '#check?' do
+ subject { system_check.check? }
+
+ before do
+ expect_next_instance_of(Gitlab::AuthorizedKeys) do |instance|
+ allow(instance).to receive(:accessible?) { accessible? }
+ end
+ end
+
+ context 'authorized keys is accessible' do
+ let(:accessible?) { true }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'authorized keys is not accessible' do
+ let(:accessible?) { false }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#repair!' do
+ subject { system_check.repair! }
+
+ before do
+ expect_next_instance_of(Gitlab::AuthorizedKeys) do |instance|
+ allow(instance).to receive(:create) { created }
+ end
+ end
+
+ context 'authorized_keys file created' do
+ let(:created) { true }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'authorized_keys file is not created' do
+ let(:created) { false }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 6cba7df114c..56fa26d5f23 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -187,6 +187,22 @@ describe Notify do
end
end
+ describe 'that are due soon' do
+ subject { described_class.issue_due_email(recipient.id, issue.id) }
+
+ before do
+ issue.update(due_date: Date.tomorrow)
+ end
+
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ end
+
describe 'status changed' do
let(:status) { 'closed' }
subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index db80b85360f..4f7a6d102b8 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -355,6 +355,71 @@ describe ApplicationSetting do
end
end
end
+
+ context 'asset proxy settings' do
+ before do
+ subject.asset_proxy_enabled = true
+ end
+
+ describe '#asset_proxy_url' do
+ it { is_expected.not_to allow_value('').for(:asset_proxy_url) }
+ it { is_expected.to allow_value(http).for(:asset_proxy_url) }
+ it { is_expected.to allow_value(https).for(:asset_proxy_url) }
+ it { is_expected.not_to allow_value(ftp).for(:asset_proxy_url) }
+
+ it 'is not required when asset proxy is disabled' do
+ subject.asset_proxy_enabled = false
+ subject.asset_proxy_url = ''
+
+ expect(subject).to be_valid
+ end
+ end
+
+ describe '#asset_proxy_secret_key' do
+ it { is_expected.not_to allow_value('').for(:asset_proxy_secret_key) }
+ it { is_expected.to allow_value('anything').for(:asset_proxy_secret_key) }
+
+ it 'is not required when asset proxy is disabled' do
+ subject.asset_proxy_enabled = false
+ subject.asset_proxy_secret_key = ''
+
+ expect(subject).to be_valid
+ end
+
+ it 'is encrypted' do
+ subject.asset_proxy_secret_key = 'shared secret'
+
+ expect(subject.encrypted_asset_proxy_secret_key).to be_present
+ expect(subject.encrypted_asset_proxy_secret_key).not_to eq(subject.asset_proxy_secret_key)
+ end
+ end
+
+ describe '#asset_proxy_whitelist' do
+ context 'when given an Array' do
+ it 'sets the domains and adds current running host' do
+ setting.asset_proxy_whitelist = ['example.com', 'assets.example.com']
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', 'assets.example.com', 'localhost'])
+ end
+ end
+
+ context 'when given a String' do
+ it 'sets multiple domains with spaces' do
+ setting.asset_proxy_whitelist = 'example.com *.example.com'
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.asset_proxy_whitelist = "example.com\n *.example.com"
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.asset_proxy_whitelist = "example.com, *.example.com"
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+ end
+ end
+ end
end
context 'restrict creating duplicates' do
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 7b35c2ffd36..5ef824b9950 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -192,6 +192,24 @@ describe Commit do
end
end
+ describe '.reference_valid?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:ref, :result) do
+ '1234567' | true
+ '123456' | false
+ '1' | false
+ '0' * 40 | true
+ 'c1acaa58bbcbc3eafe538cb8274ba387047b69f8' | true
+ 'H1acaa58bbcbc3eafe538cb8274ba387047b69f8' | false
+ nil | false
+ end
+
+ with_them do
+ it { expect(described_class.reference_valid?(ref)).to eq(result) }
+ end
+ end
+
describe '#reference_link_text' do
let(:project) { create(:project, :repository, path: 'sample-project') }
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 39680c0e51a..65d41edc035 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -46,6 +46,7 @@ describe Issuable do
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_length_of(:title).is_at_most(255) }
+ it { is_expected.to validate_length_of(:description).is_at_most(1_000_000) }
end
describe 'milestone' do
@@ -795,4 +796,29 @@ describe Issuable do
end
end
end
+
+ describe '#matches_cross_reference_regex?' do
+ context "issue description with long path string" do
+ let(:mentionable) { build(:issue, description: "/a" * 50000) }
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+
+ context "note with long path string" do
+ let(:mentionable) { build(:note, note: "/a" * 50000) }
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+
+ context "note with long path string" do
+ let(:project) { create(:project, :public, :repository) }
+ let(:mentionable) { project.commit }
+
+ before do
+ expect(mentionable.raw).to receive(:message).and_return("/a" * 50000)
+ end
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+ end
end
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index e17b98536fa..929b5f52c7c 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -272,4 +272,22 @@ describe Noteable do
expect(described_class.resolvable_types).to include('MergeRequest')
end
end
+
+ describe '#capped_notes_count' do
+ context 'notes number < 10' do
+ it 'the number of notes is returned' do
+ expect(subject.capped_notes_count(10)).to eq(9)
+ end
+ end
+
+ context 'notes number > 10' do
+ before do
+ create_list(:note, 2, project: project, noteable: subject)
+ end
+
+ it '10 is returned' do
+ expect(subject.capped_notes_count(10)).to eq(10)
+ end
+ end
+ end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index c2e2298823e..baf2cfeab0c 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -84,6 +84,13 @@ describe Label do
end
end
+ describe '#description' do
+ it 'sanitizes description' do
+ label = described_class.new(description: '<b>foo & bar?</b>')
+ expect(label.description).to eq('foo & bar?')
+ end
+ end
+
describe 'priorization' do
subject(:label) { create(:label) }
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index 18d4549977c..2429cd408a6 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -81,4 +81,83 @@ describe List do
expect(subject.title).to eq 'Closed'
end
end
+
+ describe '#update_preferences_for' do
+ let(:user) { create(:user) }
+ let(:list) { create(:list) }
+
+ context 'when user is present' do
+ context 'when there are no preferences for user' do
+ it 'creates new user preferences' do
+ expect { list.update_preferences_for(user, collapsed: true) }.to change { ListUserPreference.count }.by(1)
+ expect(list.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when there are preferences for user' do
+ it 'updates user preferences' do
+ list.update_preferences_for(user, collapsed: false)
+
+ expect { list.update_preferences_for(user, collapsed: true) }.not_to change { ListUserPreference.count }
+ expect(list.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when user is nil' do
+ it 'does not create user preferences' do
+ expect { list.update_preferences_for(nil, collapsed: true) }.not_to change { ListUserPreference.count }
+ end
+ end
+ end
+ end
+
+ describe '#preferences_for' do
+ let(:user) { create(:user) }
+ let(:list) { create(:list) }
+
+ context 'when user is nil' do
+ it 'returns not persisted preferences' do
+ preferences = list.preferences_for(nil)
+
+ expect(preferences.persisted?).to eq(false)
+ expect(preferences.list_id).to eq(list.id)
+ expect(preferences.user_id).to be_nil
+ end
+ end
+
+ context 'when a user preference already exists' do
+ before do
+ list.update_preferences_for(user, collapsed: true)
+ end
+
+ it 'loads preference for user' do
+ preferences = list.preferences_for(user)
+
+ expect(preferences).to be_persisted
+ expect(preferences.collapsed).to eq(true)
+ end
+
+ context 'when preferences are already loaded for user' do
+ it 'gets preloaded user preferences' do
+ fetched_list = described_class.where(id: list.id).with_preferences_for(user).first
+
+ expect(fetched_list).to receive(:preloaded_preferences_for).with(user).and_call_original
+
+ preferences = fetched_list.preferences_for(user)
+
+ expect(preferences.collapsed).to eq(true)
+ end
+ end
+ end
+
+ context 'when preferences for user does not exist' do
+ it 'returns not persisted preferences' do
+ preferences = list.preferences_for(user)
+
+ expect(preferences.persisted?).to eq(false)
+ expect(preferences.user_id).to eq(user.id)
+ expect(preferences.list_id).to eq(list.id)
+ end
+ end
+ end
end
diff --git a/spec/models/list_user_preference_spec.rb b/spec/models/list_user_preference_spec.rb
new file mode 100644
index 00000000000..1335a3700dc
--- /dev/null
+++ b/spec/models/list_user_preference_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ListUserPreference do
+ set(:user) { create(:user) }
+ set(:list) { create(:list) }
+
+ before do
+ list.update_preferences_for(user, { collapsed: true })
+ end
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:list) }
+ it { is_expected.to belong_to(:user) }
+
+ it do
+ is_expected.to validate_uniqueness_of(:user_id).scoped_to(:list_id)
+ .with_message("should have only one list preference per user")
+ end
+ end
+end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index bfd0e5f0558..927fbdb93d8 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -22,6 +22,7 @@ describe Note do
end
describe 'validation' do
+ it { is_expected.to validate_length_of(:note).is_at_most(1_000_000) }
it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_presence_of(:project) }
diff --git a/spec/models/project_services/discord_service_spec.rb b/spec/models/project_services/discord_service_spec.rb
index be82f223478..96ac532dcd1 100644
--- a/spec/models/project_services/discord_service_spec.rb
+++ b/spec/models/project_services/discord_service_spec.rb
@@ -8,4 +8,37 @@ describe DiscordService do
let(:client_arguments) { { url: webhook_url } }
let(:content_key) { :content }
end
+
+ describe '#execute' do
+ include StubRequests
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:webhook_url) { "https://example.gitlab.com/" }
+
+ let(:sample_data) do
+ Gitlab::DataBuilder::Push.build_sample(project, user)
+ end
+
+ before do
+ allow(subject).to receive_messages(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ webhook: webhook_url
+ )
+
+ WebMock.stub_request(:post, webhook_url)
+ end
+
+ context 'DNS rebind to local address' do
+ before do
+ stub_dns(webhook_url, ip_address: '192.168.2.120')
+ end
+
+ it 'does not allow DNS rebinding' do
+ expect { subject.execute(sample_data) }.to raise_error(ArgumentError, /is blocked/)
+ end
+ end
+ end
end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 7edeb56efe2..f8d6e500e10 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -40,6 +40,13 @@ describe RemoteMirror, :mailer do
expect(remote_mirror).to be_invalid
expect(remote_mirror.errors[:url].first).to include('Requests to the local network are not allowed')
end
+
+ it 'returns a nil safe_url' do
+ remote_mirror = build(:remote_mirror, url: 'http://[0:0:0:0:ffff:123.123.123.123]/foo.git')
+
+ expect(remote_mirror.url).to eq('http://[0:0:0:0:ffff:123.123.123.123]/foo.git')
+ expect(remote_mirror.safe_url).to be_nil
+ end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 79395fcc994..419e1dc2459 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1223,66 +1223,36 @@ describe Repository do
end
describe '#branch_exists?' do
- let(:branch) { repository.root_ref }
+ it 'uses branch_names' do
+ allow(repository).to receive(:branch_names).and_return(['foobar'])
- subject { repository.branch_exists?(branch) }
-
- it 'delegates to branch_names when the cache is empty' do
- repository.expire_branches_cache
-
- expect(repository).to receive(:branch_names).and_call_original
- is_expected.to eq(true)
- end
-
- it 'uses redis set caching when the cache is filled' do
- repository.branch_names # ensure the branch name cache is filled
-
- expect(repository)
- .to receive(:branch_names_include?)
- .with(branch)
- .and_call_original
-
- is_expected.to eq(true)
+ expect(repository.branch_exists?('foobar')).to eq(true)
+ expect(repository.branch_exists?('master')).to eq(false)
end
end
describe '#tag_exists?' do
- let(:tag) { repository.tags.first.name }
-
- subject { repository.tag_exists?(tag) }
-
- it 'delegates to tag_names when the cache is empty' do
- repository.expire_tags_cache
-
- expect(repository).to receive(:tag_names).and_call_original
- is_expected.to eq(true)
- end
-
- it 'uses redis set caching when the cache is filled' do
- repository.tag_names # ensure the tag name cache is filled
-
- expect(repository)
- .to receive(:tag_names_include?)
- .with(tag)
- .and_call_original
+ it 'uses tag_names' do
+ allow(repository).to receive(:tag_names).and_return(['foobar'])
- is_expected.to eq(true)
+ expect(repository.tag_exists?('foobar')).to eq(true)
+ expect(repository.tag_exists?('master')).to eq(false)
end
end
- describe '#branch_names', :clean_gitlab_redis_cache do
+ describe '#branch_names', :use_clean_rails_memory_store_caching do
let(:fake_branch_names) { ['foobar'] }
it 'gets cached across Repository instances' do
allow(repository.raw_repository).to receive(:branch_names).once.and_return(fake_branch_names)
- expect(repository.branch_names).to match_array(fake_branch_names)
+ expect(repository.branch_names).to eq(fake_branch_names)
fresh_repository = Project.find(project.id).repository
expect(fresh_repository.object_id).not_to eq(repository.object_id)
expect(fresh_repository.raw_repository).not_to receive(:branch_names)
- expect(fresh_repository.branch_names).to match_array(fake_branch_names)
+ expect(fresh_repository.branch_names).to eq(fake_branch_names)
end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 9aeef7c3b4b..ce17704acbd 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -121,12 +121,12 @@ describe Todo do
subject.target_type = 'Commit'
subject.commit_id = commit.id
- expect(subject.target_reference).to eq commit.reference_link_text(full: true)
+ expect(subject.target_reference).to eq commit.reference_link_text(full: false)
end
it 'returns full reference for issuables' do
subject.target = issue
- expect(subject.target_reference).to eq issue.to_reference(full: true)
+ expect(subject.target_reference).to eq issue.to_reference(full: false)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8338d2b5b39..b8c323904b8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1176,7 +1176,7 @@ describe User do
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
expect(user.external).to be_falsey
- expect(user.private_profile).to eq false
+ expect(user.private_profile).to eq(false)
end
end
@@ -3065,6 +3065,47 @@ describe User do
end
end
+ describe '#will_save_change_to_login?' do
+ let(:user) { create(:user, username: 'old-username', email: 'old-email@example.org') }
+ let(:new_username) { 'new-name' }
+ let(:new_email) { 'new-email@example.org' }
+
+ subject { user.will_save_change_to_login? }
+
+ context 'when the username is changed' do
+ before do
+ user.username = new_username
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when the email is changed' do
+ before do
+ user.email = new_email
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when both email and username are changed' do
+ before do
+ user.username = new_username
+ user.email = new_email
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when email and username aren\'t changed' do
+ before do
+ user.name = 'new_name'
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe '#sync_attribute?' do
let(:user) { described_class.new }
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index b149dbcf871..25267d36ab8 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -172,6 +172,34 @@ describe IssuePolicy do
expect(permissions(assignee, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue)
end
+ context 'when issues are private' do
+ before do
+ project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
+ end
+ let(:issue) { create(:issue, project: project, author: author) }
+ let(:visitor) { create(:user) }
+ let(:admin) { create(:user, :admin) }
+
+ it 'forbids visitors from viewing issues' do
+ expect(permissions(visitor, issue)).to be_disallowed(:read_issue)
+ end
+ it 'forbids visitors from commenting' do
+ expect(permissions(visitor, issue)).to be_disallowed(:create_note)
+ end
+ it 'allows guests to view' do
+ expect(permissions(guest, issue)).to be_allowed(:read_issue)
+ end
+ it 'allows guests to comment' do
+ expect(permissions(guest, issue)).to be_allowed(:create_note)
+ end
+ it 'allows admins to view' do
+ expect(permissions(admin, issue)).to be_allowed(:read_issue)
+ end
+ it 'allows admins to comment' do
+ expect(permissions(admin, issue)).to be_allowed(:create_note)
+ end
+ end
+
context 'with confidential issues' do
let(:confidential_issue) { create(:issue, :confidential, project: project, assignees: [assignee], author: author) }
let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index 81279225d61..87205f56589 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -6,6 +6,7 @@ describe MergeRequestPolicy do
let(:guest) { create(:user) }
let(:author) { create(:user) }
let(:developer) { create(:user) }
+ let(:non_team_member) { create(:user) }
let(:project) { create(:project, :public) }
def permissions(user, merge_request)
@@ -18,6 +19,78 @@ describe MergeRequestPolicy do
project.add_developer(developer)
end
+ MR_PERMS = %i[create_merge_request_in
+ create_merge_request_from
+ read_merge_request
+ create_note].freeze
+
+ shared_examples_for 'a denied user' do
+ let(:perms) { permissions(subject, merge_request) }
+
+ MR_PERMS.each do |thing|
+ it "cannot #{thing}" do
+ expect(perms).to be_disallowed(thing)
+ end
+ end
+ end
+
+ shared_examples_for 'a user with access' do
+ let(:perms) { permissions(subject, merge_request) }
+
+ MR_PERMS.each do |thing|
+ it "can #{thing}" do
+ expect(perms).to be_allowed(thing)
+ end
+ end
+ end
+
+ context 'when merge requests have been disabled' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+
+ before do
+ project.project_feature.update(merge_requests_access_level: ProjectFeature::DISABLED)
+ end
+
+ describe 'the author' do
+ subject { author }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a guest' do
+ subject { guest }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a developer' do
+ subject { developer }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'any other user' do
+ subject { non_team_member }
+ it_behaves_like 'a denied user'
+ end
+ end
+
+ context 'when merge requests are private' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ project.project_feature.update(merge_requests_access_level: ProjectFeature::PRIVATE)
+ end
+
+ describe 'a non-team-member' do
+ subject { non_team_member }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a developer' do
+ subject { developer }
+ it_behaves_like 'a user with access'
+ end
+ end
+
context 'when merge request is unlocked' do
let(:merge_request) { create(:merge_request, :closed, source_project: project, target_project: project, author: author) }
@@ -48,6 +121,22 @@ describe MergeRequestPolicy do
it 'prevents guests from reopening merge request' do
expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request)
end
+
+ context 'when the user is not a project member' do
+ let(:user) { create(:user) }
+
+ it 'cannot create a note' do
+ expect(permissions(user, merge_request_locked)).to be_disallowed(:create_note)
+ end
+ end
+
+ context 'when the user is project member, with at least guest access' do
+ let(:user) { guest }
+
+ it 'can create a note' do
+ expect(permissions(user, merge_request_locked)).to be_allowed(:create_note)
+ end
+ end
end
context 'with external authorization enabled' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 3ab1818bebb..c94f6d22e74 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -925,19 +925,20 @@ describe API::Internal do
it 'returns link to create new merge request' do
post api('/internal/post_receive'), params: valid_params
- expect(json_response['merge_request_urls']).to match [{
- "branch_name" => branch_name,
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}",
- "new_merge_request" => true
- }]
+ message = <<~MESSAGE.strip
+ To create a merge request for #{branch_name}, visit:
+ http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}
+ MESSAGE
+
+ expect(json_response['messages']).to include(build_basic_message(message))
end
- it 'returns empty array if printing_merge_request_link_enabled is false' do
+ it 'returns no merge request messages if printing_merge_request_link_enabled is false' do
project.update!(printing_merge_request_link_enabled: false)
post api('/internal/post_receive'), params: valid_params
- expect(json_response['merge_request_urls']).to eq([])
+ expect(json_response['messages']).to be_blank
end
it 'does not invoke MergeRequests::PushOptionsHandlerService' do
@@ -968,11 +969,12 @@ describe API::Internal do
it 'links to the newly created merge request' do
post api('/internal/post_receive'), params: valid_params
- expect(json_response['merge_request_urls']).to match [{
- 'branch_name' => branch_name,
- 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/1",
- 'new_merge_request' => false
- }]
+ message = <<~MESSAGE.strip
+ View merge request for #{branch_name}:
+ http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/1
+ MESSAGE
+
+ expect(json_response['messages']).to include(build_basic_message(message))
end
it 'adds errors on the service instance to warnings' do
@@ -982,7 +984,8 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params
- expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error')
+ message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error"
+ expect(json_response['messages']).to include(build_alert_message(message))
end
it 'adds ActiveRecord errors on invalid MergeRequest records to warnings' do
@@ -995,38 +998,39 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params
- expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error')
+ message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error"
+ expect(json_response['messages']).to include(build_alert_message(message))
end
end
context 'broadcast message exists' do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
- it 'returns one broadcast message' do
+ it 'outputs a broadcast message' do
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response['broadcast_message']).to eq(broadcast_message.message)
+ expect(json_response['messages']).to include(build_alert_message(broadcast_message.message))
end
end
context 'broadcast message does not exist' do
- it 'returns empty string' do
+ it 'does not output a broadcast message' do
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response['broadcast_message']).to eq(nil)
+ expect(has_alert_messages?(json_response['messages'])).to be_falsey
end
end
context 'nil broadcast message' do
- it 'returns empty string' do
+ it 'does not output a broadcast message' do
allow(BroadcastMessage).to receive(:current).and_return(nil)
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response['broadcast_message']).to eq(nil)
+ expect(has_alert_messages?(json_response['messages'])).to be_falsey
end
end
@@ -1038,8 +1042,7 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response["redirected_message"]).to be_present
- expect(json_response["redirected_message"]).to eq(project_moved.message)
+ expect(json_response['messages']).to include(build_basic_message(project_moved.message))
end
end
@@ -1051,8 +1054,7 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response["project_created_message"]).to be_present
- expect(json_response["project_created_message"]).to eq(project_created.message)
+ expect(json_response['messages']).to include(build_basic_message(project_created.message))
end
end
@@ -1172,4 +1174,18 @@ describe API::Internal do
}
)
end
+
+ def build_alert_message(message)
+ { 'type' => 'alert', 'message' => message }
+ end
+
+ def build_basic_message(message)
+ { 'type' => 'basic', 'message' => message }
+ end
+
+ def has_alert_messages?(messages)
+ messages.any? do |message|
+ message['type'] == 'alert'
+ end
+ end
end
diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb
index 44b5ee1f130..2857715cdbe 100644
--- a/spec/requests/api/project_snapshots_spec.rb
+++ b/spec/requests/api/project_snapshots_spec.rb
@@ -6,6 +6,12 @@ describe API::ProjectSnapshots do
let(:project) { create(:project) }
let(:admin) { create(:admin) }
+ before do
+ allow(Feature::Gitaly).to receive(:server_feature_flags).and_return({
+ 'gitaly-feature-foobar' => 'true'
+ })
+ end
+
describe 'GET /projects/:id/snapshot' do
def expect_snapshot_response_for(repository)
type, params = workhorse_send_data
@@ -13,6 +19,7 @@ describe API::ProjectSnapshots do
expect(type).to eq('git-snapshot')
expect(params).to eq(
'GitalyServer' => {
+ 'features' => { 'gitaly-feature-foobar' => 'true' },
'address' => Gitlab::GitalyClient.address(repository.project.repository_storage),
'token' => Gitlab::GitalyClient.token(repository.project.repository_storage)
},
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 29f69b6ce20..58a28e636f1 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -96,6 +96,28 @@ describe API::ProjectSnippets do
}
end
+ context 'with a regular user' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE])
+ params['visibility'] = 'internal'
+ end
+
+ it 'creates a new snippet' do
+ post api("/projects/#{project.id}/snippets/", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ snippet = ProjectSnippet.find(json_response['id'])
+ expect(snippet.content).to eq(params[:code])
+ expect(snippet.description).to eq(params[:description])
+ expect(snippet.title).to eq(params[:title])
+ expect(snippet.file_name).to eq(params[:file_name])
+ expect(snippet.visibility_level).to eq(Snippet::INTERNAL)
+ end
+ end
+
it 'creates a new snippet' do
post api("/projects/#{project.id}/snippets/", admin), params: params
@@ -108,6 +130,29 @@ describe API::ProjectSnippets do
expect(snippet.visibility_level).to eq(Snippet::PUBLIC)
end
+ it 'creates a new snippet with content parameter' do
+ params[:content] = params.delete(:code)
+
+ post api("/projects/#{project.id}/snippets/", admin), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ snippet = ProjectSnippet.find(json_response['id'])
+ expect(snippet.content).to eq(params[:content])
+ expect(snippet.description).to eq(params[:description])
+ expect(snippet.title).to eq(params[:title])
+ expect(snippet.file_name).to eq(params[:file_name])
+ expect(snippet.visibility_level).to eq(Snippet::PUBLIC)
+ end
+
+ it 'returns 400 when both code and content parameters specified' do
+ params[:content] = params[:code]
+
+ post api("/projects/#{project.id}/snippets/", admin), params: params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('code, content are mutually exclusive')
+ end
+
it 'returns 400 for missing parameters' do
params.delete(:title)
@@ -167,7 +212,20 @@ describe API::ProjectSnippets do
new_content = 'New content'
new_description = 'New description'
- put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content, description: new_description }
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content, description: new_description, visibility: 'private' }
+
+ expect(response).to have_gitlab_http_status(200)
+ snippet.reload
+ expect(snippet.content).to eq(new_content)
+ expect(snippet.description).to eq(new_description)
+ expect(snippet.visibility).to eq('private')
+ end
+
+ it 'updates snippet with content parameter' do
+ new_content = 'New content'
+ new_description = 'New description'
+
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { content: new_content, description: new_description }
expect(response).to have_gitlab_http_status(200)
snippet.reload
@@ -175,6 +233,13 @@ describe API::ProjectSnippets do
expect(snippet.description).to eq(new_description)
end
+ it 'returns 400 when both code and content parameters specified' do
+ put api("/projects/#{snippet.project.id}/snippets/1234", admin), params: { code: 'some content', content: 'other content' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('code, content are mutually exclusive')
+ end
+
it 'returns 404 for invalid snippet id' do
put api("/projects/#{snippet.project.id}/snippets/1234", admin), params: { title: 'foo' }
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 590107d5161..048d04cdefd 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -224,5 +224,33 @@ describe API::Settings, 'Settings' do
expect(json_response['error']).to eq('plantuml_url is missing')
end
end
+
+ context 'asset_proxy settings' do
+ it 'updates application settings' do
+ put api('/application/settings', admin),
+ params: {
+ asset_proxy_enabled: true,
+ asset_proxy_url: 'http://assets.example.com',
+ asset_proxy_secret_key: 'shared secret',
+ asset_proxy_whitelist: ['example.com', '*.example.com']
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['asset_proxy_enabled']).to be(true)
+ expect(json_response['asset_proxy_url']).to eq('http://assets.example.com')
+ expect(json_response['asset_proxy_secret_key']).to be_nil
+ expect(json_response['asset_proxy_whitelist']).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'allows a string for asset_proxy_whitelist' do
+ put api('/application/settings', admin),
+ params: {
+ asset_proxy_whitelist: 'example.com, *.example.com'
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['asset_proxy_whitelist']).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+ end
end
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index d600076e9fb..cc05b8d5b45 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -193,18 +193,32 @@ describe API::Snippets do
}
end
- it 'creates a new snippet' do
- expect do
- post api("/snippets/", user), params: params
- end.to change { PersonalSnippet.count }.by(1)
+ shared_examples 'snippet creation' do
+ it 'creates a new snippet' do
+ expect do
+ post api("/snippets/", user), params: params
+ end.to change { PersonalSnippet.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq(params[:title])
+ expect(json_response['description']).to eq(params[:description])
+ expect(json_response['file_name']).to eq(params[:file_name])
+ expect(json_response['visibility']).to eq(params[:visibility])
+ end
+ end
+
+ context 'with restricted visibility settings' do
+ before do
+ stub_application_setting(restricted_visibility_levels:
+ [Gitlab::VisibilityLevel::INTERNAL,
+ Gitlab::VisibilityLevel::PRIVATE])
+ end
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq(params[:title])
- expect(json_response['description']).to eq(params[:description])
- expect(json_response['file_name']).to eq(params[:file_name])
- expect(json_response['visibility']).to eq(params[:visibility])
+ it_behaves_like 'snippet creation'
end
+ it_behaves_like 'snippet creation'
+
it 'returns 400 for missing parameters' do
params.delete(:title)
@@ -253,18 +267,33 @@ describe API::Snippets do
create(:personal_snippet, author: user, visibility_level: visibility_level)
end
- it 'updates snippet' do
- new_content = 'New content'
- new_description = 'New description'
+ shared_examples 'snippet updates' do
+ it 'updates a snippet' do
+ new_content = 'New content'
+ new_description = 'New description'
- put api("/snippets/#{snippet.id}", user), params: { content: new_content, description: new_description }
+ put api("/snippets/#{snippet.id}", user), params: { content: new_content, description: new_description, visibility: 'internal' }
- expect(response).to have_gitlab_http_status(200)
- snippet.reload
- expect(snippet.content).to eq(new_content)
- expect(snippet.description).to eq(new_description)
+ expect(response).to have_gitlab_http_status(200)
+ snippet.reload
+ expect(snippet.content).to eq(new_content)
+ expect(snippet.description).to eq(new_description)
+ expect(snippet.visibility).to eq('internal')
+ end
end
+ context 'with restricted visibility settings' do
+ before do
+ stub_application_setting(restricted_visibility_levels:
+ [Gitlab::VisibilityLevel::PUBLIC,
+ Gitlab::VisibilityLevel::PRIVATE])
+ end
+
+ it_behaves_like 'snippet updates'
+ end
+
+ it_behaves_like 'snippet updates'
+
it 'returns 404 for invalid snippet id' do
put api("/snippets/1234", user), params: { title: 'foo' }
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index 27df42c0aee..ce20d494542 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -19,6 +19,15 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
SOURCE
end
+ it 'flags the use of `prepend_if_ee QA::EE` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend_if_ee 'QA::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
it 'does not flag the use of `prepend_if_ee EEFoo` in the middle of a file' do
expect_no_offenses(<<~SOURCE)
class Foo
@@ -176,6 +185,16 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
SOURCE
end
+ it 'disallows the use of prepend to inject a QA::EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend(QA::EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
it 'disallows the use of extend to inject an EE module' do
expect_offense(<<~SOURCE)
class Foo
diff --git a/spec/rubocop/cop/rspec/be_success_matcher_spec.rb b/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
new file mode 100644
index 00000000000..12aa7d1643e
--- /dev/null
+++ b/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_relative '../../../../rubocop/cop/rspec/be_success_matcher'
+
+describe RuboCop::Cop::RSpec::BeSuccessMatcher do
+ include CopHelper
+
+ let(:source_file) { 'spec/foo_spec.rb' }
+
+ subject(:cop) { described_class.new }
+
+ shared_examples 'cop' do |good:, bad:|
+ context "using #{bad} call" do
+ it 'registers an offense' do
+ inspect_source(bad, source_file)
+
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq([bad])
+ end
+
+ it "autocorrects it to `#{good}`" do
+ autocorrected = autocorrect_source(bad, source_file)
+
+ expect(autocorrected).to eql(good)
+ end
+ end
+
+ context "using #{good} call" do
+ it 'does not register an offense' do
+ inspect_source(good)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+
+ include_examples 'cop',
+ bad: 'expect(response).to be_success',
+ good: 'expect(response).to be_successful'
+
+ include_examples 'cop',
+ bad: 'expect(response).to_not be_success',
+ good: 'expect(response).to_not be_successful'
+
+ include_examples 'cop',
+ bad: 'expect(response).not_to be_success',
+ good: 'expect(response).not_to be_successful'
+
+ include_examples 'cop',
+ bad: 'is_expected.to be_success',
+ good: 'is_expected.to be_successful'
+
+ include_examples 'cop',
+ bad: 'is_expected.to_not be_success',
+ good: 'is_expected.to_not be_successful'
+
+ include_examples 'cop',
+ bad: 'is_expected.not_to be_success',
+ good: 'is_expected.not_to be_successful'
+end
diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb
index 276e0f6ff3d..d1483c3c41e 100644
--- a/spec/serializers/merge_request_serializer_spec.rb
+++ b/spec/serializers/merge_request_serializer_spec.rb
@@ -41,6 +41,14 @@ describe MergeRequestSerializer do
end
end
+ context 'noteable merge request serialization' do
+ let(:serializer) { 'noteable' }
+
+ it 'matches noteable merge request json schema' do
+ expect(json_entity).to match_schema('entities/merge_request_noteable', strict: true)
+ end
+ end
+
context 'no serializer' do
let(:serializer) { nil }
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index ab06c1a1209..51fb43907a6 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -110,6 +110,39 @@ describe ApplicationSettings::UpdateService do
end
end
+ describe 'markdown cache invalidators' do
+ shared_examples 'invalidates markdown cache' do |attribute|
+ let(:params) { attribute }
+
+ it 'increments cache' do
+ expect { subject.execute }.to change(application_settings, :local_markdown_version).by(1)
+ end
+ end
+
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_enabled: true }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_url: 'http://test.com' }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_secret_key: 'another secret' }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_whitelist: ['domain.com'] }
+
+ context 'when also setting the local_markdown_version' do
+ let(:params) { { asset_proxy_enabled: true, local_markdown_version: 12 } }
+
+ it 'does not increment' do
+ expect { subject.execute }.to change(application_settings, :local_markdown_version).to(12)
+ end
+ end
+
+ context 'do not invalidate if value does not change' do
+ let(:params) { { asset_proxy_enabled: true, asset_proxy_secret_key: 'secret', asset_proxy_url: 'http://test.com' } }
+
+ it 'does not increment' do
+ described_class.new(application_settings, admin, params).execute
+
+ expect { described_class.new(application_settings, admin, params).execute }.not_to change(application_settings, :local_markdown_version)
+ end
+ end
+ end
+
describe 'performance bar settings' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index 2ebfd295fa2..2535f339495 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -3,13 +3,15 @@
require 'spec_helper'
describe Boards::Lists::ListService do
+ let(:user) { create(:user) }
+
describe '#execute' do
context 'when board parent is a project' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:label) { create(:label, project: project) }
let!(:list) { create(:list, board: board, label: label) }
- let(:service) { described_class.new(project, double) }
+ let(:service) { described_class.new(project, user) }
it_behaves_like 'lists list service'
end
@@ -19,7 +21,7 @@ describe Boards::Lists::ListService do
let(:board) { create(:board, group: group) }
let(:label) { create(:group_label, group: group) }
let!(:list) { create(:list, board: board, label: label) }
- let(:service) { described_class.new(group, double) }
+ let(:service) { described_class.new(group, user) }
it_behaves_like 'lists list service'
end
diff --git a/spec/services/boards/lists/update_service_spec.rb b/spec/services/boards/lists/update_service_spec.rb
new file mode 100644
index 00000000000..f28bbab941a
--- /dev/null
+++ b/spec/services/boards/lists/update_service_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Boards::Lists::UpdateService do
+ let(:user) { create(:user) }
+ let!(:list) { create(:list, board: board, position: 0) }
+
+ shared_examples 'moving list' do
+ context 'when user can admin list' do
+ it 'calls Lists::MoveService to update list position' do
+ board.parent.add_developer(user)
+ service = described_class.new(board.parent, user, position: 1)
+
+ expect(Boards::Lists::MoveService).to receive(:new).with(board.parent, user, { position: 1 }).and_call_original
+ expect_any_instance_of(Boards::Lists::MoveService).to receive(:execute).with(list)
+
+ service.execute(list)
+ end
+ end
+
+ context 'when user cannot admin list' do
+ it 'does not call Lists::MoveService to update list position' do
+ service = described_class.new(board.parent, user, position: 1)
+
+ expect(Boards::Lists::MoveService).not_to receive(:new)
+
+ service.execute(list)
+ end
+ end
+ end
+
+ shared_examples 'updating list preferences' do
+ context 'when user can read list' do
+ it 'updates list preference for user' do
+ board.parent.add_guest(user)
+ service = described_class.new(board.parent, user, collapsed: true)
+
+ service.execute(list)
+
+ expect(list.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when user cannot read list' do
+ it 'does not update list preference for user' do
+ service = described_class.new(board.parent, user, collapsed: true)
+
+ service.execute(list)
+
+ expect(list.preferences_for(user).collapsed).to be_nil
+ end
+ end
+ end
+
+ describe '#execute' do
+ context 'when position parameter is present' do
+ context 'for projects' do
+ it_behaves_like 'moving list' do
+ let(:project) { create(:project, :private) }
+ let(:board) { create(:board, project: project) }
+ end
+ end
+
+ context 'for groups' do
+ it_behaves_like 'moving list' do
+ let(:group) { create(:group, :private) }
+ let(:board) { create(:board, group: group) }
+ end
+ end
+ end
+
+ context 'when collapsed parameter is present' do
+ context 'for projects' do
+ it_behaves_like 'updating list preferences' do
+ let(:project) { create(:project, :private) }
+ let(:board) { create(:board, project: project) }
+ end
+ end
+
+ context 'for groups' do
+ it_behaves_like 'updating list preferences' do
+ let(:group) { create(:group, :private) }
+ let(:board) { create(:board, group: group) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index a54bd85a11a..464a67649ff 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -14,7 +14,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:phase) { a_phase }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
context "when phase is #{a_phase}" do
@@ -44,7 +44,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
before do
application.update!(cluster: cluster)
- expect(service).to receive(:installation_phase).and_raise(error)
+ expect(service).to receive(:pod_phase).and_raise(error)
end
include_examples 'logs kubernetes errors' do
@@ -77,7 +77,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
context 'when installation POD succeeded' do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'removes the installation POD' do
@@ -101,7 +101,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:errors) { 'test installation failed' }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -116,7 +116,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:application) { create(:clusters_applications_helm, :timed_out, :updating) }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -138,7 +138,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
context 'when installation POD succeeded' do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'removes the installation POD' do
@@ -162,7 +162,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:errors) { 'test installation failed' }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -177,7 +177,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:application) { create(:clusters_applications_helm, :timed_out) }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
index a948b442441..1a9f7089c3d 100644
--- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
@@ -20,7 +20,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
let(:phase) { a_phase }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
context "when phase is #{a_phase}" do
@@ -47,7 +47,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
context 'when installation POD succeeded' do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'removes the installation POD' do
@@ -95,7 +95,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
let(:errors) { 'test installation failed' }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -110,7 +110,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
let(:application) { create(:clusters_applications_prometheus, :timed_out, :uninstalling) }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -131,7 +131,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
before do
application.update!(cluster: cluster)
- expect(service).to receive(:installation_phase).and_raise(error)
+ expect(service).to receive(:pod_phase).and_raise(error)
end
include_examples 'logs kubernetes errors' do
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index 9b83f65a17e..7d2491b3a49 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -34,6 +34,19 @@ describe CreateSnippetService do
expect(snippet.errors.any?).to be_falsey
expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
+
+ describe "when visibility level is passed as a string" do
+ before do
+ @opts[:visibility] = 'internal'
+ @opts.delete(:visibility_level)
+ end
+
+ it "assigns the correct visibility level" do
+ snippet = create_snippet(nil, @user, @opts)
+ expect(snippet.errors.any?).to be_falsey
+ expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ end
+ end
end
describe 'usage counter' do
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 6874a8a0929..642a49d57d5 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -60,35 +60,63 @@ describe Issues::CloseService do
describe '#close_issue' do
context "closed by a merge request" do
- before do
+ it 'mentions closure via a merge request' do
perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
end
- end
- it 'mentions closure via a merge request' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference))
end
+
+ context 'when user cannot read merge request' do
+ it 'does not mention merge request' do
+ project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+ perform_enqueued_jobs do
+ described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
+ end
+
+ email = ActionMailer::Base.deliveries.last
+ body_text = email.body.parts.map(&:body).join(" ")
+
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
+ expect(body_text).not_to include(closing_merge_request.to_reference)
+ end
+ end
end
context "closed by a commit" do
- before do
+ it 'mentions closure via a commit' do
perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
end
- end
- it 'mentions closure via a commit' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
expect(email.body.parts.map(&:body)).to all(include(closing_commit.id))
end
+
+ context 'when user cannot read the commit' do
+ it 'does not mention the commit id' do
+ project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+ perform_enqueued_jobs do
+ described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
+ end
+
+ email = ActionMailer::Base.deliveries.last
+ body_text = email.body.parts.map(&:body).join(" ")
+
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
+ expect(body_text).not_to include(closing_commit.id)
+ end
+ end
end
context "valid params" do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index d4fa62fa85d..8178b7d2ba2 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -183,27 +183,65 @@ describe Projects::CreateService, '#execute' do
context 'restricted visibility level' do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
- opts.merge!(
- visibility_level: Gitlab::VisibilityLevel::PUBLIC
- )
+ shared_examples 'restricted visibility' do
+ it 'does not allow a restricted visibility level for non-admins' do
+ project = create_project(user, opts)
+
+ expect(project).to respond_to(:errors)
+ expect(project.errors.messages).to have_key(:visibility_level)
+ expect(project.errors.messages[:visibility_level].first).to(
+ match('restricted by your GitLab administrator')
+ )
+ end
+
+ it 'allows a restricted visibility level for admins' do
+ admin = create(:admin)
+ project = create_project(admin, opts)
+
+ expect(project.errors.any?).to be(false)
+ expect(project.saved?).to be(true)
+ end
end
- it 'does not allow a restricted visibility level for non-admins' do
- project = create_project(user, opts)
- expect(project).to respond_to(:errors)
- expect(project.errors.messages).to have_key(:visibility_level)
- expect(project.errors.messages[:visibility_level].first).to(
- match('restricted by your GitLab administrator')
- )
+ context 'when visibility is project based' do
+ before do
+ opts.merge!(
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ )
+ end
+
+ include_examples 'restricted visibility'
end
- it 'allows a restricted visibility level for admins' do
- admin = create(:admin)
- project = create_project(admin, opts)
+ context 'when visibility is overridden' do
+ let(:visibility) { 'public' }
- expect(project.errors.any?).to be(false)
- expect(project.saved?).to be(true)
+ before do
+ opts.merge!(
+ import_data: {
+ data: {
+ override_params: {
+ visibility: visibility
+ }
+ }
+ }
+ )
+ end
+
+ include_examples 'restricted visibility'
+
+ context 'when visibility is misspelled' do
+ let(:visibility) { 'publik' }
+
+ it 'does not restrict project creation' do
+ project = create_project(user, opts)
+
+ expect(project.errors.any?).to be(false)
+ expect(project.saved?).to be(true)
+ end
+ end
end
end
diff --git a/spec/services/projects/forks_count_service_spec.rb b/spec/services/projects/forks_count_service_spec.rb
index 7e35648e9ff..1b44782468a 100644
--- a/spec/services/projects/forks_count_service_spec.rb
+++ b/spec/services/projects/forks_count_service_spec.rb
@@ -2,15 +2,17 @@
require 'spec_helper'
-describe Projects::ForksCountService do
+describe Projects::ForksCountService, :use_clean_rails_memory_store_caching do
+ let(:project) { build(:project) }
+ subject { described_class.new(project) }
+
+ it_behaves_like 'a counter caching service'
+
describe '#count' do
it 'returns the number of forks' do
- project = build(:project, id: 42)
- service = described_class.new(project)
-
- allow(service).to receive(:uncached_count).and_return(1)
+ allow(subject).to receive(:uncached_count).and_return(1)
- expect(service.count).to eq(1)
+ expect(subject.count).to eq(1)
end
end
end
diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
index 849601c4a63..66233787d3a 100644
--- a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
@@ -30,5 +30,23 @@ describe Projects::LfsPointers::LfsLinkService do
expect(subject.execute(new_oid_list.keys)).to eq linked
end
+
+ it 'links in batches' do
+ stub_const("#{described_class}::BATCH_SIZE", 3)
+
+ expect(Gitlab::Import::Logger)
+ .to receive(:info)
+ .with(class: described_class.name,
+ project_id: project.id,
+ project_path: project.full_path,
+ lfs_objects_linked_count: 7,
+ iterations: 3)
+
+ lfs_objects = create_list(:lfs_object, 7)
+ linked = subject.execute(lfs_objects.pluck(:oid))
+
+ expect(project.all_lfs_objects.count).to eq 9
+ expect(linked.size).to eq 7
+ end
end
end
diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb
index 8efa34765d0..593a4df1f8f 100644
--- a/spec/services/projects/open_issues_count_service_spec.rb
+++ b/spec/services/projects/open_issues_count_service_spec.rb
@@ -2,10 +2,13 @@
require 'spec_helper'
-describe Projects::OpenIssuesCountService do
- describe '#count' do
- let(:project) { create(:project) }
+describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching do
+ let(:project) { create(:project) }
+ subject { described_class.new(project) }
+
+ it_behaves_like 'a counter caching service'
+ describe '#count' do
context 'when user is nil' do
it 'does not include confidential issues in the issue count' do
create(:issue, :opened, project: project)
@@ -53,9 +56,7 @@ describe Projects::OpenIssuesCountService do
end
end
- context '#refresh_cache', :use_clean_rails_memory_store_caching do
- let(:subject) { described_class.new(project) }
-
+ context '#refresh_cache' do
before do
create(:issue, :opened, project: project)
create(:issue, :opened, project: project)
diff --git a/spec/services/projects/open_merge_requests_count_service_spec.rb b/spec/services/projects/open_merge_requests_count_service_spec.rb
index 0d8227f7db5..f9fff4cbd4c 100644
--- a/spec/services/projects/open_merge_requests_count_service_spec.rb
+++ b/spec/services/projects/open_merge_requests_count_service_spec.rb
@@ -2,16 +2,21 @@
require 'spec_helper'
-describe Projects::OpenMergeRequestsCountService do
+describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching do
+ set(:project) { create(:project) }
+
+ subject { described_class.new(project) }
+
+ it_behaves_like 'a counter caching service'
+
describe '#count' do
it 'returns the number of open merge requests' do
- project = create(:project)
create(:merge_request,
:opened,
source_project: project,
target_project: project)
- expect(described_class.new(project).count).to eq(1)
+ expect(subject.count).to eq(1)
end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index f46f9633c1c..910fe3b50b7 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -212,6 +212,13 @@ describe SystemNoteService do
expect(build_note([assignee, assignee1, assignee2], [assignee, assignee1])).to eq \
"unassigned @#{assignee2.username}"
end
+
+ it 'builds a correct phrase when the locale is different' do
+ Gitlab::I18n.with_locale('pt-BR') do
+ expect(build_note([assignee, assignee1, assignee2], [assignee3])).to eq \
+ "assigned to @#{assignee3.username} and unassigned @#{assignee.username}, @#{assignee1.username}, and @#{assignee2.username}"
+ end
+ end
end
describe '.change_milestone' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 9ee23f3eb48..bdf2f59704c 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -436,25 +436,114 @@ describe TodoService do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
end
- context 'on commit' do
- let(:project) { create(:project, :repository) }
-
- it 'creates a todo for each valid mentioned user when leaving a note on commit' do
- service.new_note(note_on_commit, john_doe)
-
- should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ context 'commits' do
+ let(:base_commit_todo_attrs) { { target_id: nil, target_type: 'Commit', author: john_doe } }
+
+ context 'leaving a note on a commit in a public project' do
+ let(:project) { create(:project, :repository, :public) }
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_create_todo(expected_todo.merge(user: non_member))
+ end
end
- it 'creates a directly addressed todo for each valid mentioned user when leaving a note on commit' do
- service.new_note(addressed_note_on_commit, john_doe)
+ context 'leaving a note on a commit in a public project with private code' do
+ let(:project) { create(:project, :repository, :public, :repository_private) }
+
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+ end
- should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ context 'leaving a note on a commit in a private project' do
+ let(:project) { create(:project, :repository, :private) }
+
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_not_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_not_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
end
end
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 0678f54c195..19b35dca6a7 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -32,12 +32,25 @@ describe UpdateSnippetService do
expect(@snippet.visibility_level).to eq(old_visibility)
end
- it 'admins should be able to update to pubic visibility' do
+ it 'admins should be able to update to public visibility' do
old_visibility = @snippet.visibility_level
update_snippet(@project, @admin, @snippet, @opts)
expect(@snippet.visibility_level).not_to eq(old_visibility)
expect(@snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
+
+ describe "when visibility level is passed as a string" do
+ before do
+ @opts[:visibility] = 'internal'
+ @opts.delete(:visibility_level)
+ end
+
+ it "assigns the correct visibility level" do
+ update_snippet(@project, @user, @snippet, @opts)
+ expect(@snippet.errors.any?).to be_falsey
+ expect(@snippet.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ end
+ end
end
describe 'usage counter' do
diff --git a/spec/services/users/keys_count_service_spec.rb b/spec/services/users/keys_count_service_spec.rb
index bee8380e8b7..6b7493f343f 100644
--- a/spec/services/users/keys_count_service_spec.rb
+++ b/spec/services/users/keys_count_service_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
let(:user) { create(:user) }
- let(:service) { described_class.new(user) }
+ subject { described_class.new(user) }
+
+ it_behaves_like 'a counter caching service'
describe '#count' do
before do
@@ -12,53 +14,19 @@ describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
end
it 'returns the number of SSH keys as an Integer' do
- expect(service.count).to eq(1)
- end
-
- it 'caches the number of keys in Redis', :request_store do
- service.delete_cache
- control_count = ActiveRecord::QueryRecorder.new { service.count }.count
- service.delete_cache
-
- expect { 2.times { service.count } }.not_to exceed_query_limit(control_count)
- end
- end
-
- describe '#refresh_cache' do
- it 'refreshes the Redis cache' do
- Rails.cache.write(service.cache_key, 10)
- service.refresh_cache
-
- expect(Rails.cache.fetch(service.cache_key, raw: true)).to be_zero
- end
- end
-
- describe '#delete_cache' do
- it 'removes the cache' do
- service.count
- service.delete_cache
-
- expect(Rails.cache.fetch(service.cache_key, raw: true)).to be_nil
+ expect(subject.count).to eq(1)
end
end
describe '#uncached_count' do
it 'returns the number of SSH keys' do
- expect(service.uncached_count).to be_zero
- end
-
- it 'does not cache the number of keys' do
- recorder = ActiveRecord::QueryRecorder.new do
- 2.times { service.uncached_count }
- end
-
- expect(recorder.count).to be > 0
+ expect(subject.uncached_count).to be_zero
end
end
describe '#cache_key' do
it 'returns the cache key' do
- expect(service.cache_key).to eq("users/key-count-service/#{user.id}")
+ expect(subject.cache_key).to eq("users/key-count-service/#{user.id}")
end
end
end
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 5590bf0fb7e..f070243f111 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -73,7 +73,7 @@ shared_examples 'thread comments' do |resource_name|
expect(page).not_to have_selector menu_selector
find(toggle_selector).click
- execute_script("document.querySelector('body').click()")
+ find("#{form_selector} .note-textarea").click
expect(page).not_to have_selector menu_selector
end
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index d936dc6de41..9d47a0c23df 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -8,7 +8,10 @@ module ActiveRecord
@log = []
@cached = []
@skip_cached = skip_cached
- ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block)
+ # force replacement of bind parameters to give tests the ability to check for ids
+ ActiveRecord::Base.connection.unprepared_statement do
+ ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block)
+ end
end
def show_backtrace(values)
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index dec7898d8d2..f364e4fd158 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -105,6 +105,10 @@ module StubConfiguration
allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages))
end
+ def stub_asset_proxy_setting(messages)
+ allow(Gitlab.config.asset_proxy).to receive_messages(to_settings(messages))
+ end
+
def stub_rack_attack_setting(messages)
allow(Gitlab.config.rack_attack).to receive(:git_basic_auth).and_return(messages)
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
index 38f6011646e..e7fee7239fc 100644
--- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
@@ -6,9 +6,10 @@ RSpec.shared_context 'GroupProjectsFinder context' do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:current_user) { create(:user) }
+ let(:params) { {} }
let(:options) { {} }
- let(:finder) { described_class.new(group: group, current_user: current_user, options: options) }
+ let(:finder) { described_class.new(group: group, current_user: current_user, params: params, options: options) }
let!(:public_project) { create(:project, :public, group: group, path: '1') }
let!(:private_project) { create(:project, :private, group: group, path: '2') }
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 39d13cccb13..4bc22861d58 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -7,6 +7,8 @@ shared_examples 'handle uploads' do
let(:secret) { FileUploader.generate_secret }
let(:uploader_class) { FileUploader }
+ it_behaves_like 'handle uploads authorize'
+
describe "POST #create" do
context 'when a user is not authorized to upload a file' do
it 'returns 404 status' do
@@ -271,7 +273,9 @@ shared_examples 'handle uploads' do
end
end
end
+end
+shared_examples 'handle uploads authorize' do
describe "POST #authorize" do
context 'when a user is not authorized to upload a file' do
it 'returns 404 status' do
@@ -284,7 +288,12 @@ shared_examples 'handle uploads' do
context 'when a user can upload a file' do
before do
sign_in(user)
- model.add_developer(user)
+
+ if model.is_a?(PersonalSnippet)
+ model.update!(author: user)
+ else
+ model.add_developer(user)
+ end
end
context 'and the request bypassed workhorse' do
diff --git a/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb b/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb
new file mode 100644
index 00000000000..b1ecd4fd007
--- /dev/null
+++ b/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'HTML text with references' do
+ let(:markdown_prepend) { "&lt;img src=\"\" onerror=alert(`bug`)&gt;" }
+
+ it 'preserves escaped HTML text and adds valid references' do
+ reference = resource.to_reference(format: :name)
+
+ doc = reference_filter("#{markdown_prepend}#{reference}")
+
+ expect(doc.to_html).to start_with(markdown_prepend)
+ expect(doc.text).to eq %(<img src="" onerror=alert(`bug`)>#{resource_text})
+ end
+
+ it 'preserves escaped HTML text if there are no valid references' do
+ reference = "#{resource.class.reference_prefix}invalid"
+ text = "#{markdown_prepend}#{reference}"
+
+ doc = reference_filter(text)
+
+ expect(doc.to_html).to eq text
+ end
+end
diff --git a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
new file mode 100644
index 00000000000..9604555c57d
--- /dev/null
+++ b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
@@ -0,0 +1,8 @@
+shared_examples_for 'matches_cross_reference_regex? fails fast' do
+ it 'fails fast for long strings' do
+ # took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172823
+ expect do
+ Timeout.timeout(3.seconds) { mentionable.matches_cross_reference_regex? }
+ end.not_to raise_error
+ end
+end
diff --git a/spec/support/shared_examples/services/count_service_shared_examples.rb b/spec/support/shared_examples/services/count_service_shared_examples.rb
new file mode 100644
index 00000000000..9bea180a778
--- /dev/null
+++ b/spec/support/shared_examples/services/count_service_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+# The calling spec should use `:use_clean_rails_memory_store_caching`
+# when including this shared example. E.g.:
+#
+# describe MyCountService, :use_clean_rails_memory_store_caching do
+# it_behaves_like 'a counter caching service'
+# end
+shared_examples 'a counter caching service' do
+ describe '#count' do
+ it 'caches the count', :request_store do
+ subject.delete_cache
+ control_count = ActiveRecord::QueryRecorder.new { subject.count }.count
+ subject.delete_cache
+
+ expect { 2.times { subject.count } }.not_to exceed_query_limit(control_count)
+ end
+ end
+
+ describe '#refresh_cache' do
+ it 'refreshes the cache' do
+ original_count = subject.count
+ Rails.cache.write(subject.cache_key, original_count + 1, raw: subject.raw?)
+
+ subject.refresh_cache
+
+ expect(fetch_cache || 0).to eq(original_count)
+ end
+ end
+
+ describe '#delete_cache' do
+ it 'removes the cache' do
+ subject.count
+ subject.delete_cache
+
+ expect(fetch_cache).to be_nil
+ end
+ end
+
+ describe '#uncached_count' do
+ it 'does not cache the count' do
+ subject.delete_cache
+ subject.uncached_count
+
+ expect(fetch_cache).to be_nil
+ end
+ end
+
+ private
+
+ def fetch_cache
+ Rails.cache.read(subject.cache_key, raw: subject.raw?)
+ end
+end
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index 66c064e3fba..5d521d18c70 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -7,6 +7,7 @@ describe 'devise/shared/_signin_box' do
assign(:ldap_servers, [])
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
allow(view).to receive(:captcha_enabled?).and_return(false)
+ allow(view).to receive(:captcha_on_login_required?).and_return(false)
end
it 'is shown when Crowd is enabled' do
diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb
index 47804411b9d..0da3470433c 100644
--- a/spec/views/groups/edit.html.haml_spec.rb
+++ b/spec/views/groups/edit.html.haml_spec.rb
@@ -23,7 +23,7 @@ describe 'groups/edit.html.haml' do
render
expect(rendered).to have_content("Prevent sharing a project within #{test_group.name} with other groups")
- expect(rendered).to have_css('.descr', text: 'help text here')
+ expect(rendered).to have_css('.js-descr', text: 'help text here')
expect(rendered).to have_field('group_share_with_group_lock', checkbox_options)
end
end
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 0c445aeac88..20aacff75fd 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -67,7 +67,6 @@
@babel/template,7.1.2,MIT
@babel/traverse,7.1.0,MIT
@babel/types,7.1.2,MIT
-@gitlab/csslab,1.8.0,MIT
@gitlab/svgs,1.41.0,MIT
@gitlab/ui,1.15.0,MIT
@sindresorhus/is,0.7.0,MIT
diff --git a/yarn.lock b/yarn.lock
index 712cdace0d7..78f9b461c36 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -964,13 +964,6 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
-"@gitlab/csslab@^1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.9.0.tgz#22fca5b1a30cbd9ca46fc6f9485ecbaba4dc300c"
- integrity sha512-Zjayzokm7E2wgxUR/pxIMocdiBB5XHt2PEemdzD8qD+aQmMpMxSyIEMQk5Jq0Wgv+Rd5WXTolTw3kmb9l8ZeJg==
- dependencies:
- bootstrap "^4.1.3"
-
"@gitlab/eslint-config@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.6.0.tgz#1fd247d6ab477d53d4c330e05f007e3afa303689"
@@ -998,15 +991,15 @@
dependencies:
vue-eslint-parser "^6.0.4"
-"@gitlab/svgs@^1.70.0":
- version "1.70.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.70.0.tgz#bdae478148c15d955fc06e69fd5d5ecae8298943"
- integrity sha512-0uV9fgTwe17Fyy0hTcrsGX2jJuCrz3uRIe8yffuqc6pbQrSfYJyN66mfCCB45wq8lKTgOB5q0qcUyJx3RQEcKg==
+"@gitlab/svgs@^1.71.0":
+ version "1.71.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.71.0.tgz#c8e6e8f500ea91e5cbba4ac08df533fb2e622a00"
+ integrity sha512-kkeNic/FFwaqKnzwio4NE7whBOZ/toRJ8cS0587DBotajAzSYhph5ij4TCY2GTjPa33zIJ5OUr/k90C0Kr71hQ==
-"@gitlab/ui@5.19.0":
- version "5.19.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.19.0.tgz#ef5431e0e91eb4009a30edcf49a40716cc2570d9"
- integrity sha512-gCeTymtVzzO2uOWZGIweLM5JcHq3TN1QwP61CXw7b9Lp5A2MysRb8upehtR5d4JLOf/GQg8rHQB26uRvUc+AXQ==
+"@gitlab/ui@5.20.1":
+ version "5.20.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.20.1.tgz#a7d479f2a19988eacd90a4864183f0c49ddd310f"
+ integrity sha512-fbqs5ncnqItgmXLlK4/ZoXwW+6DGBf+/nub2agoIT4EocwYGtrmAB8V+ybEcRrJVG8YpkCGt6R4lD24VrATWTw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
@@ -1021,7 +1014,7 @@
vue "^2.6.10"
vue-loader "^15.4.2"
-"@gitlab/visual-review-tools@^1.0.0":
+"@gitlab/visual-review-tools@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.0.0.tgz#6012e0a19797c1f5dad34ccf4dacdaf38e400a73"
integrity sha512-xMvz9IwrXisQ1MH+Tj6lfbQcQSiQy88nTPuQV6OTLBGuV+vIQeVwXeIkQeTKuSpd0GqZvigPdRqxyQCa3blpIg==
@@ -1637,7 +1630,7 @@ ansi-align@^2.0.0:
dependencies:
string-width "^2.0.0"
-ansi-colors@^3.0.0, ansi-colors@^3.2.4:
+ansi-colors@^3.0.0:
version "3.2.4"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
@@ -1647,13 +1640,6 @@ ansi-escapes@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
-ansi-gray@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251"
- integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE=
- dependencies:
- ansi-wrap "0.1.0"
-
ansi-html@0.0.7, ansi-html@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@@ -1686,11 +1672,6 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-ansi-wrap@0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
- integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
-
anymatch@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -2280,7 +2261,7 @@ bootstrap-vue@2.0.0-rc.27:
portal-vue "^2.1.5"
vue-functional-data-merge "^3.1.0"
-bootstrap@4.3.1, bootstrap@^4.1.3, bootstrap@^4.3.1:
+bootstrap@4.3.1, bootstrap@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
@@ -2985,11 +2966,6 @@ color-name@1.1.3, color-name@^1.0.0:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-color-support@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
- integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
-
colors@^1.1.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
@@ -5027,16 +5003,6 @@ extsprintf@1.3.0, extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
-fancy-log@^1.3.3:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7"
- integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==
- dependencies:
- ansi-gray "^0.1.1"
- color-support "^1.1.3"
- parse-node-version "^1.0.0"
- time-stamp "^1.0.0"
-
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
@@ -5782,16 +5748,6 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
-gulp-print@^5.0.2:
- version "5.0.2"
- resolved "https://registry.yarnpkg.com/gulp-print/-/gulp-print-5.0.2.tgz#8f379148218d2e168461baa74352e11d1bf7aa75"
- integrity sha512-iIpHMzC/b3gFvVXOfP9Jk94SWGIsDLVNUrxULRleQev+08ug07mh84b1AOlW6QDQdmInQiqDFqJN1UvhU2nXdg==
- dependencies:
- ansi-colors "^3.2.4"
- fancy-log "^1.3.3"
- map-stream "0.0.7"
- vinyl "^2.2.0"
-
gzip-size@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80"
@@ -7965,11 +7921,6 @@ map-obj@^2.0.0:
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9"
integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk=
-map-stream@0.0.7:
- version "0.0.7"
- resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8"
- integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=
-
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
@@ -8165,10 +8116,10 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
-mermaid@^8.2.3:
- version "8.2.3"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.2.3.tgz#609bad45bedc3ee1a935161c11c3c22689cfecd9"
- integrity sha512-G2p9BAAEeTtogPs4YXM8KyX+TsZULlgk0tGvmBPfBZ5j3YCPxgAxG9ZzleiYNItF7M1hGkE485BDLN8DbfR+/Q==
+mermaid@^8.2.4:
+ version "8.2.4"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.2.4.tgz#52bcd45611fd8552ab9ac4e385d2766a0e38dcf7"
+ integrity sha512-2la1eJhu4n+Uug4zbxFnkETFDJ9U32OY/fRP8g8A1DrRdfT3Er+7CuUSvxfhIDxl+AxSEU4dXdqCiToZAVMCmQ==
dependencies:
"@braintree/sanitize-url" "^3.1.0"
d3 "^5.7.0"
@@ -8176,7 +8127,6 @@ mermaid@^8.2.3:
dagre-layout "^0.8.8"
documentation "^12.0.1"
graphlibrary "^2.2.0"
- gulp-print "^5.0.2"
he "^1.2.0"
lodash "^4.17.11"
minify "^4.1.1"
@@ -9145,11 +9095,6 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
-parse-node-version@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
- integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
-
parse-passwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@@ -11783,11 +11728,6 @@ thunky@^0.1.0:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
integrity sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=
-time-stamp@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"
- integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=
-
timeago.js@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-3.0.2.tgz#32a67e7c0d887ea42ca588d3aae26f77de5e76cc"
@@ -12618,7 +12558,7 @@ vinyl-sourcemap@^1.1.0:
remove-bom-buffer "^3.0.0"
vinyl "^2.0.0"
-vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.0:
+vinyl@^2.0.0, vinyl@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86"
integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==