summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock8
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js3
-rw-r--r--app/assets/javascripts/boards/index.js4
-rw-r--r--app/assets/javascripts/boards/models/assignee.js (renamed from app/assets/javascripts/vue_shared/models/assignee.js)0
-rw-r--r--app/assets/javascripts/boards/models/issue.js2
-rw-r--r--app/assets/javascripts/boards/models/label.js11
-rw-r--r--app/assets/javascripts/boards/models/list.js4
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js11
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js6
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js16
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue83
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue2
-rw-r--r--app/assets/javascripts/clusters/services/application_state_machine.js1
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js11
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue15
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue21
-rw-r--r--app/assets/javascripts/emoji/no_emoji_validator.js44
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue34
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue43
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue2
-rw-r--r--app/assets/javascripts/ide/lib/keymap.json8
-rw-r--r--app/assets/javascripts/ide/stores/actions.js5
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js5
-rw-r--r--app/assets/javascripts/ide/stores/getters.js5
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js23
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js7
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutations.js18
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/state.js1
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js9
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js2
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js6
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue8
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js1
-rw-r--r--app/assets/javascripts/notes.js25
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue12
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue4
-rw-r--r--app/assets/javascripts/notes/components/discussion_locked_widget.vue20
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue2
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue45
-rw-r--r--app/assets/javascripts/notes/mixins/issuable_state.js11
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/project.js12
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue5
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js2
-rw-r--r--app/assets/javascripts/pages/sessions/new/length_validator.js32
-rw-r--r--app/assets/javascripts/reports/components/issues_list.vue20
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue42
-rw-r--r--app/assets/javascripts/reports/constants.js6
-rw-r--r--app/assets/javascripts/validators/input_validator.js34
-rw-r--r--app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue89
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue13
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue45
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/modal_copy_button.vue121
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/models/label.js13
-rw-r--r--app/assets/stylesheets/components/popover.scss19
-rw-r--r--app/assets/stylesheets/framework/animations.scss24
-rw-r--r--app/assets/stylesheets/framework/blocks.scss9
-rw-r--r--app/assets/stylesheets/framework/buttons.scss3
-rw-r--r--app/assets/stylesheets/framework/common.scss47
-rw-r--r--app/assets/stylesheets/framework/files.scss22
-rw-r--r--app/assets/stylesheets/framework/lists.scss8
-rw-r--r--app/assets/stylesheets/framework/mixins.scss10
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss52
-rw-r--r--app/assets/stylesheets/framework/timeline.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/_ide_mixins.scss14
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss9
-rw-r--r--app/assets/stylesheets/pages/diff.scss4
-rw-r--r--app/assets/stylesheets/pages/groups.scss3
-rw-r--r--app/assets/stylesheets/pages/issuable.scss10
-rw-r--r--app/assets/stylesheets/pages/issues.scss11
-rw-r--r--app/assets/stylesheets/pages/login.scss3
-rw-r--r--app/assets/stylesheets/pages/members.scss59
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss35
-rw-r--r--app/assets/stylesheets/pages/note_form.scss30
-rw-r--r--app/assets/stylesheets/pages/notes.scss66
-rw-r--r--app/controllers/acme_challenges_controller.rb17
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/clusters/clusters_controller.rb3
-rw-r--r--app/controllers/projects/clusters_controller.rb4
-rw-r--r--app/controllers/projects/environments_controller.rb4
-rw-r--r--app/controllers/projects/git_http_client_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb10
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb3
-rw-r--r--app/controllers/search_controller.rb1
-rw-r--r--app/graphql/types/base_field.rb15
-rw-r--r--app/helpers/ci_variables_helper.rb4
-rw-r--r--app/helpers/emails_helper.rb6
-rw-r--r--app/helpers/visibility_level_helper.rb38
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/concerns/maskable.rb4
-rw-r--r--app/models/concerns/taskable.rb7
-rw-r--r--app/models/diff_note.rb2
-rw-r--r--app/models/merge_request.rb15
-rw-r--r--app/models/notification_recipient.rb8
-rw-r--r--app/models/notification_setting.rb9
-rw-r--r--app/models/pages_domain.rb11
-rw-r--r--app/models/pages_domain_acme_order.rb24
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_ci_cd_setting.rb18
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/models/project_services/youtrack_service.rb6
-rw-r--r--app/presenters/ci/build_runner_presenter.rb18
-rw-r--r--app/serializers/issue_entity.rb8
-rw-r--r--app/services/auto_merge/base_service.rb52
-rw-r--r--app/services/auto_merge/merge_when_pipeline_succeeds_service.rb34
-rw-r--r--app/services/merge_requests/base_service.rb26
-rw-r--r--app/services/merge_requests/create_pipeline_service.rb37
-rw-r--r--app/services/pages_domains/create_acme_order_service.rb31
-rw-r--r--app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb41
-rw-r--r--app/services/preview_markdown_service.rb4
-rw-r--r--app/services/projects/fork_service.rb20
-rw-r--r--app/services/search_service.rb4
-rw-r--r--app/views/abuse_reports/new.html.haml8
-rw-r--r--app/views/ci/variables/_index.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml12
-rw-r--r--app/views/discussions/_notes.html.haml20
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml32
-rw-r--r--app/views/notify/new_user_email.html.haml3
-rw-r--r--app/views/notify/new_user_email.text.erb11
-rw-r--r--app/views/profiles/preferences/show.html.haml21
-rw-r--r--app/views/projects/_merge_request_merge_checks_settings.html.haml2
-rw-r--r--app/views/projects/_new_project_fields.html.haml2
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml2
-rw-r--r--app/views/projects/diffs/_text_file.html.haml2
-rw-r--r--app/views/projects/issues/_new_branch.html.haml8
-rw-r--r--app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml43
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml8
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/search/results/_milestone.html.haml2
-rw-r--r--app/views/shared/members/_group.html.haml19
-rw-r--r--app/views/shared/members/_member.html.haml30
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml5
-rw-r--r--changelogs/unreleased/37495.yml5
-rw-r--r--changelogs/unreleased/51636-task-list-api-pderichs.yml5
-rw-r--r--changelogs/unreleased/55033-discussion-system-note-alignment.yml5
-rw-r--r--changelogs/unreleased/55125-mr-tab-scrolling.yml5
-rw-r--r--changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml5
-rw-r--r--changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml5
-rw-r--r--changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml5
-rw-r--r--changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml5
-rw-r--r--changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml5
-rw-r--r--changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml2
-rw-r--r--changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml2
-rw-r--r--changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml2
-rw-r--r--changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml5
-rw-r--r--changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml4
-rw-r--r--changelogs/unreleased/62091-remove-time-windows-flag.yml5
-rw-r--r--changelogs/unreleased/62124-new-threaded-discussion-design.yml5
-rw-r--r--changelogs/unreleased/62418-project-default-git-depth.yml5
-rw-r--r--changelogs/unreleased/62656-adjusted-dropdown-styles.yml5
-rw-r--r--changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml5
-rw-r--r--changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml5
-rw-r--r--changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml5
-rw-r--r--changelogs/unreleased/ci-variable-conjunction.yml5
-rw-r--r--changelogs/unreleased/copy-button-in-modals.yml5
-rw-r--r--changelogs/unreleased/docs-add-chatops-request-doc.yml5
-rw-r--r--changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml5
-rw-r--r--changelogs/unreleased/fix-allow-lower-case-issue-ids.yml5
-rw-r--r--changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml5
-rw-r--r--changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml5
-rw-r--r--changelogs/unreleased/fix-time-window-default.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-profiles-preferences.yml5
-rw-r--r--changelogs/unreleased/improve-email-text-part.yml5
-rw-r--r--changelogs/unreleased/sh-default-visibility-fix.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-55869.yml5
-rw-r--r--changelogs/unreleased/sh-fix-openid-connect-defaults.yml5
-rw-r--r--changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-shell-9-3-0.yml5
-rw-r--r--changelogs/unreleased/update-psd-doc.yml5
-rw-r--r--config/environments/development.rb3
-rw-r--r--config/initializers/7_prometheus_metrics.rb6
-rw-r--r--config/routes.rb2
-rw-r--r--danger/commit_messages/Dangerfile4
-rw-r--r--danger/roulette/Dangerfile10
-rw-r--r--db/migrate/20190429082448_create_pages_domain_acme_orders.rb28
-rw-r--r--db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb16
-rw-r--r--db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb11
-rw-r--r--db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb34
-rw-r--r--db/schema.rb18
-rw-r--r--doc/administration/auth/oidc.md58
-rw-r--r--doc/administration/high_availability/database.md101
-rw-r--r--doc/administration/job_artifacts.md11
-rw-r--r--doc/administration/logs.md2
-rw-r--r--doc/api/README.md142
-rw-r--r--doc/api/issues.md50
-rw-r--r--doc/api/jobs.md103
-rw-r--r--doc/api/merge_requests.md76
-rw-r--r--doc/api/project_clusters.md2
-rw-r--r--doc/api/project_level_variables.md43
-rw-r--r--doc/api/search.md193
-rw-r--r--doc/ci/README.md27
-rw-r--r--doc/ci/caching/index.md24
-rw-r--r--doc/ci/chatops/README.md27
-rw-r--r--doc/ci/ci_cd_for_external_repos/bitbucket_integration.md49
-rw-r--r--doc/ci/ci_cd_for_external_repos/github_integration.md49
-rw-r--r--doc/ci/ci_cd_for_external_repos/index.md24
-rw-r--r--doc/ci/docker/README.md4
-rw-r--r--doc/ci/docker/using_docker_build.md42
-rw-r--r--doc/ci/docker/using_docker_images.md63
-rw-r--r--doc/ci/docker/using_kaniko.md42
-rw-r--r--doc/ci/enable_or_disable_ci.md59
-rw-r--r--doc/ci/environments.md4
-rw-r--r--doc/ci/environments/protected_environments.md16
-rw-r--r--doc/ci/multi_project_pipelines.md8
-rw-r--r--doc/ci/pipelines.md3
-rw-r--r--doc/ci/variables/README.md20
-rw-r--r--doc/ci/yaml/README.md4
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/chatops_on_gitlabcom.md21
-rw-r--r--doc/development/fe_guide/frontend_faq.md14
-rw-r--r--doc/development/feature_flags.md2
-rw-r--r--doc/development/i18n/externalization.md4
-rw-r--r--doc/development/i18n/proofreader.md2
-rw-r--r--doc/development/testing_guide/end_to_end/dynamic_element_validation.md113
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md39
-rw-r--r--doc/development/understanding_explain_plans.md7
-rw-r--r--doc/integration/elasticsearch.md138
-rw-r--r--doc/topics/git/how_to_install_git/index.md29
-rw-r--r--doc/topics/git/index.md57
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/index.md121
-rw-r--r--doc/topics/git/troubleshooting_git.md39
-rw-r--r--doc/update/upgrading_from_source.md60
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md8
-rw-r--r--doc/user/admin_area/settings/index.md12
-rw-r--r--doc/user/admin_area/settings/visibility_and_access_controls.md46
-rw-r--r--doc/user/application_security/dast/index.md2
-rw-r--r--doc/user/application_security/dependency_scanning/index.md11
-rw-r--r--doc/user/application_security/img/dismissed_info.pngbin0 -> 20244 bytes
-rw-r--r--doc/user/application_security/img/interactive_reports.pngbin23190 -> 93910 bytes
-rw-r--r--doc/user/application_security/index.md10
-rw-r--r--doc/user/application_security/security_dashboard/img/project_security_dashboard.pngbin49062 -> 126356 bytes
-rw-r--r--doc/user/group/clusters/index.md14
-rw-r--r--doc/user/group/custom_project_templates.md16
-rw-r--r--doc/user/group/epics/index.md16
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/group/insights/index.md16
-rw-r--r--doc/user/group/roadmap/index.md27
-rw-r--r--doc/user/group/saml_sso/index.md30
-rw-r--r--doc/user/group/subgroups/index.md57
-rw-r--r--doc/user/profile/account/delete_account.md2
-rw-r--r--doc/user/project/clusters/serverless/index.md2
-rw-r--r--doc/user/project/insights/index.md3
-rw-r--r--doc/user/project/integrations/img/jira_add_user_to_group.pngbin24838 -> 266180 bytes
-rw-r--r--doc/user/project/integrations/img/jira_added_user_to_group.pngbin0 -> 82473 bytes
-rw-r--r--doc/user/project/integrations/img/jira_create_new_group.pngbin19127 -> 262453 bytes
-rw-r--r--doc/user/project/integrations/img/jira_create_new_user.pngbin12625 -> 173516 bytes
-rw-r--r--doc/user/project/integrations/img/jira_group_access.pngbin19147 -> 112706 bytes
-rw-r--r--doc/user/project/integrations/img/jira_user_management_link.pngbin23906 -> 206155 bytes
-rw-r--r--doc/user/project/integrations/jira.md4
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md45
-rw-r--r--doc/user/project/integrations/youtrack.md4
-rw-r--r--doc/user/project/labels.md8
-rw-r--r--doc/user/project/merge_requests/allow_collaboration.md16
-rw-r--r--doc/user/project/merge_requests/authorization_for_merge_requests.md18
-rw-r--r--doc/user/project/merge_requests/browser_performance_testing.md20
-rw-r--r--doc/user/project/merge_requests/cherry_pick_changes.md26
-rw-r--r--doc/user/project/merge_requests/code_quality.md24
-rw-r--r--doc/user/project/merge_requests/fast_forward_merge.md35
-rw-r--r--doc/user/project/merge_requests/index.md13
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md42
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md23
-rw-r--r--doc/user/project/merge_requests/resolve_conflicts.md16
-rw-r--r--doc/user/project/merge_requests/revert_changes.md29
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md45
-rw-r--r--doc/user/project/merge_requests/versions.md34
-rw-r--r--doc/user/project/merge_requests/work_in_progress_merge_requests.md24
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md9
-rw-r--r--doc/user/project/pipelines/job_artifacts.md16
-rw-r--r--doc/user/project/pipelines/settings.md16
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/helpers/pagination.rb2
-rw-r--r--lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb40
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/and.rb27
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/base.rb6
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/equals.rb9
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/matches.rb25
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb9
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb9
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/operator.rb20
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/or.rb27
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb13
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexer.rb19
-rw-r--r--lib/gitlab/ci/pipeline/expression/parser.rb70
-rw-r--r--lib/gitlab/ci/pipeline/expression/statement.rb26
-rw-r--r--lib/gitlab/ci/templates/Serverless.gitlab-ci.yml18
-rw-r--r--lib/gitlab/danger/helper.rb4
-rw-r--r--lib/gitlab/danger/teammate.rb27
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb6
-rw-r--r--lib/gitlab/lets_encrypt/challenge.rb2
-rw-r--r--lib/gitlab/lets_encrypt/order.rb11
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb2
-rw-r--r--lib/gitlab/omniauth_initializer.rb6
-rw-r--r--lib/gitlab/search_results.rb4
-rw-r--r--lib/gitlab/setup_helper.rb8
-rw-r--r--lib/tasks/gettext.rake1
-rw-r--r--locale/gitlab.pot143
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb4
-rw-r--r--qa/qa/resource/repository/project_push.rb2
-rw-r--r--spec/controllers/acme_challenges_controller_spec.rb44
-rw-r--r--spec/controllers/application_controller_spec.rb34
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb14
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb2
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb15
-rw-r--r--spec/factories/pages_domain_acme_orders.rb17
-rw-r--r--spec/features/admin/admin_appearance_spec.rb2
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb2
-rw-r--r--spec/features/groups/merge_requests_spec.rb2
-rw-r--r--spec/features/instance_statistics/conversational_development_index_spec.rb2
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_merge_request_spec.rb2
-rw-r--r--spec/features/project_variables_spec.rb2
-rw-r--r--spec/features/projects/clusters_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb6
-rw-r--r--spec/features/projects/new_project_spec.rb30
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb10
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb2
-rw-r--r--spec/features/projects_spec.rb2
-rw-r--r--spec/features/security/profile_access_spec.rb2
-rw-r--r--spec/features/users/signup_spec.rb44
-rw-r--r--spec/frontend/boards/modal_store_spec.js4
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js33
-rw-r--r--spec/frontend/clusters/components/application_row_spec.js76
-rw-r--r--spec/frontend/clusters/services/application_state_machine_spec.js2
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js3
-rw-r--r--spec/frontend/helpers/timeout.js16
-rw-r--r--spec/frontend/ide/stores/modules/commit/mutations_spec.js15
-rw-r--r--spec/frontend/lib/utils/number_utility_spec.js11
-rw-r--r--spec/frontend/reports/components/report_section_spec.js40
-rw-r--r--spec/frontend/test_setup.js2
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_warning_spec.js18
-rw-r--r--spec/frontend/vue_shared/components/modal_copy_button_spec.js40
-rw-r--r--spec/frontend/vue_shared/droplab_dropdown_button_spec.js136
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb22
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb2
-rw-r--r--spec/graphql/types/base_field_spec.rb29
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb45
-rw-r--r--spec/javascripts/boards/board_card_spec.js4
-rw-r--r--spec/javascripts/boards/boards_store_spec.js4
-rw-r--r--spec/javascripts/boards/issue_card_spec.js4
-rw-r--r--spec/javascripts/boards/issue_spec.js4
-rw-r--r--spec/javascripts/boards/list_spec.js4
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js8
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js59
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/actions_spec.js43
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js73
-rw-r--r--spec/javascripts/ide/mock_data.js1
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js16
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js5
-rw-r--r--spec/javascripts/ide/stores/getters_spec.js32
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js157
-rw-r--r--spec/javascripts/ide/stores/modules/commit/getters_spec.js29
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js94
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js2
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb34
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb7
-rw-r--r--spec/lib/gitlab/background_migration/delete_diff_files_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb120
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb60
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb120
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb94
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb237
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb8
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb47
-rw-r--r--spec/lib/gitlab/lets_encrypt/challenge_spec.rb18
-rw-r--r--spec/lib/gitlab/lets_encrypt/order_spec.rb38
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb8
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb7
-rw-r--r--spec/migrations/enqueue_verify_pages_domain_workers_spec.rb6
-rw-r--r--spec/migrations/remove_orphaned_label_links_spec.rb6
-rw-r--r--spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb46
-rw-r--r--spec/models/ci/build_spec.rb4
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb16
-rw-r--r--spec/models/pages_domain_acme_order_spec.rb49
-rw-r--r--spec/models/pages_domain_spec.rb11
-rw-r--r--spec/models/project_ci_cd_setting_spec.rb28
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb54
-rw-r--r--spec/requests/api/jobs_spec.rb4
-rw-r--r--spec/requests/api/runner_spec.rb16
-rw-r--r--spec/requests/api/task_completion_status_spec.rb85
-rw-r--r--spec/services/auto_merge/base_service_spec.rb144
-rw-r--r--spec/services/merge_requests/create_pipeline_service_spec.rb71
-rw-r--r--spec/services/pages_domains/create_acme_order_service_spec.rb63
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb146
-rw-r--r--spec/services/preview_markdown_service_spec.rb4
-rw-r--r--spec/services/projects/fork_service_spec.rb24
-rw-r--r--spec/services/suggestions/create_service_spec.rb2
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb4
-rw-r--r--spec/support/helpers/lets_encrypt_helpers.rb40
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb (renamed from spec/support/shared_context/policies/project_policy_shared_context.rb)0
-rw-r--r--spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb116
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb8
424 files changed, 6906 insertions, 2122 deletions
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index 9084fa2f716..26aaba0e866 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-1.1.0
+1.2.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index deeb3d66ef0..b13d146a7b0 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-9.2.0
+9.3.0
diff --git a/Gemfile b/Gemfile
index 195d06502a4..c22a3191c3c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -275,7 +275,7 @@ gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
-gem 'sentry-raven', '~> 2.7'
+gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.9.7'
@@ -336,7 +336,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.7.0'
gem 'factory_bot_rails', '~> 4.8.2'
gem 'rspec-rails', '~> 3.7.0'
- gem 'rspec-retry', '~> 0.4.5'
+ gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
gem 'rspec-parameterized', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 11e71cfb80e..785a77bc202 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -798,8 +798,8 @@ GEM
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-support (~> 3.7.0)
- rspec-retry (0.4.5)
- rspec-core
+ rspec-retry (0.6.1)
+ rspec-core (> 3.3)
rspec-set (0.1.3)
rspec-support (3.7.1)
rspec_junit_formatter (0.4.1)
@@ -1199,7 +1199,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.7.0)
- rspec-retry (~> 0.4.5)
+ rspec-retry (~> 0.6.1)
rspec-set (~> 0.1.3)
rspec_junit_formatter
rspec_profiling (~> 0.0.5)
@@ -1217,7 +1217,7 @@ DEPENDENCIES
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
selenium-webdriver (~> 3.141)
- sentry-raven (~> 2.7)
+ sentry-raven (~> 2.9)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 4.0.1)
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index a5ed695af35..c8a9cb1c296 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -2,7 +2,6 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
-import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store';
@@ -78,8 +77,6 @@ export default function initNewListDropdown() {
color: label.color,
},
});
-
- boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
}
},
});
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index e9cab3e3bba..f2f37d22b97 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -3,8 +3,8 @@ import Vue from 'vue';
import Flash from '~/flash';
import { __ } from '~/locale';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import './models/label';
+import './models/assignee';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
diff --git a/app/assets/javascripts/vue_shared/models/assignee.js b/app/assets/javascripts/boards/models/assignee.js
index 4a29b0d0581..4a29b0d0581 100644
--- a/app/assets/javascripts/vue_shared/models/assignee.js
+++ b/app/assets/javascripts/boards/models/assignee.js
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index f8ff20cb0cd..f858b162c6b 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -4,7 +4,7 @@
/* global ListAssignee */
import Vue from 'vue';
-import '~/vue_shared/models/label';
+import './label';
import { isEE, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import IssueProject from './project';
import boardsStore from '../stores/boards_store';
diff --git a/app/assets/javascripts/boards/models/label.js b/app/assets/javascripts/boards/models/label.js
new file mode 100644
index 00000000000..cd2a2c0137f
--- /dev/null
+++ b/app/assets/javascripts/boards/models/label.js
@@ -0,0 +1,11 @@
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+
+export default class ListLabel {
+ constructor(obj) {
+ Object.assign(this, convertObjectPropsToCamelCase(obj, { dropKeys: ['priority'] }), {
+ priority: obj.priority !== null ? obj.priority : Infinity,
+ });
+ }
+}
+
+window.ListLabel = ListLabel;
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 08aecfab8a4..a9d88f19146 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -2,8 +2,8 @@
/* global ListIssue */
import { __ } from '~/locale';
-import ListLabel from '~/vue_shared/models/label';
-import ListAssignee from '~/vue_shared/models/assignee';
+import ListLabel from './label';
+import ListAssignee from './assignee';
import { isEE, urlParamsToObject } from '~/lib/utils/common_utils';
import boardsStore from '../stores/boards_store';
import ListMilestone from './milestone';
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
index 592e1fd1c31..0bba2a2e160 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -27,15 +27,24 @@ function generateErrorBoxContent(errors) {
// Used for the variable list on CI/CD projects/groups settings page
export default class AjaxVariableList {
- constructor({ container, saveButton, errorBox, formField = 'variables', saveEndpoint }) {
+ constructor({
+ container,
+ saveButton,
+ errorBox,
+ formField = 'variables',
+ saveEndpoint,
+ maskableRegex,
+ }) {
this.container = container;
this.saveButton = saveButton;
this.errorBox = errorBox;
this.saveEndpoint = saveEndpoint;
+ this.maskableRegex = maskableRegex;
this.variableList = new VariableList({
container: this.container,
formField,
+ maskableRegex,
});
this.bindEvents();
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index 0390a3bf96a..0303e4e51dd 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -16,9 +16,10 @@ function createEnvironmentItem(value) {
}
export default class VariableList {
- constructor({ container, formField }) {
+ constructor({ container, formField, maskableRegex }) {
this.$container = $(container);
this.formField = formField;
+ this.maskableRegex = new RegExp(maskableRegex);
this.environmentDropdownMap = new WeakMap();
this.inputMap = {
@@ -196,9 +197,8 @@ export default class VariableList {
validateMaskability($row) {
const invalidInputClass = 'gl-field-error-outline';
- const maskableRegex = /^\w{8,}$/; // Eight or more alphanumeric characters plus underscores
const variableValue = $row.find(this.inputMap.secret_value.selector).val();
- const isValueMaskable = maskableRegex.test(variableValue) || variableValue === '';
+ const isValueMaskable = this.maskableRegex.test(variableValue) || variableValue === '';
const isMaskedChecked = $row.find(this.inputMap.masked.selector).val() === 'true';
// Show a validation error if the user wants to mask an unmaskable variable value
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index bc2e71b99f2..aacfa0d87e6 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -142,8 +142,7 @@ export default class Clusters {
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
- eventHub.$on('upgradeApplication', data => this.upgradeApplication(data));
- eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId));
+ eventHub.$on('updateApplication', data => this.updateApplication(data));
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
@@ -155,8 +154,7 @@ export default class Clusters {
removeListeners() {
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication);
- eventHub.$off('upgradeApplication', this.upgradeApplication);
- eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess);
+ eventHub.$off('updateApplication', this.updateApplication);
eventHub.$off('saveKnativeDomain');
eventHub.$off('setKnativeHostname');
eventHub.$off('uninstallApplication');
@@ -331,19 +329,13 @@ export default class Clusters {
});
}
- upgradeApplication(data) {
- const appId = data.id;
-
+ updateApplication({ id: appId, params }) {
this.store.updateApplication(appId);
- this.service.installApplication(appId, data.params).catch(() => {
+ this.service.installApplication(appId, params).catch(() => {
this.store.notifyUpdateFailure(appId);
});
}
- dismissUpgradeSuccess(appId) {
- this.store.acknowledgeSuccessfulUpdate(appId);
- }
-
toggleIngressDomainHelpText({ externalIp }, { externalIp: newExternalIp }) {
if (externalIp !== newExternalIp) {
this.ingressDomainHelpText.classList.toggle('hide', !newExternalIp);
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 7b173be599a..4771090aa7e 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -2,7 +2,7 @@
/* eslint-disable vue/require-default-prop */
import { GlLink, GlModalDirective } from '@gitlab/ui';
import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
-import { s__, sprintf } from '../../locale';
+import { s__, __, sprintf } from '~/locale';
import eventHub from '../event_hub';
import identicon from '../../vue_shared/components/identicon.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
@@ -85,7 +85,7 @@ export default {
type: String,
required: false,
},
- upgradeAvailable: {
+ updateAvailable: {
type: Boolean,
required: false,
},
@@ -113,11 +113,6 @@ export default {
required: false,
default: false,
},
- updateAcknowledged: {
- type: Boolean,
- required: false,
- default: true,
- },
installApplicationRequestParams: {
type: Object,
required: false,
@@ -174,11 +169,11 @@ export default {
installButtonLabel() {
let label;
if (this.canInstall) {
- label = s__('ClusterIntegration|Install');
+ label = __('Install');
} else if (this.isInstalling) {
- label = s__('ClusterIntegration|Installing');
+ label = __('Installing');
} else if (this.installed) {
- label = s__('ClusterIntegration|Installed');
+ label = __('Installed');
}
return label;
@@ -187,7 +182,7 @@ export default {
return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
},
manageButtonLabel() {
- return s__('ClusterIntegration|Manage');
+ return __('Manage');
},
hasError() {
return this.installFailed || this.uninstallFailed;
@@ -207,42 +202,42 @@ export default {
},
versionLabel() {
if (this.updateFailed) {
- return s__('ClusterIntegration|Upgrade failed');
- } else if (this.isUpgrading) {
- return s__('ClusterIntegration|Upgrading');
+ return __('Update failed');
+ } else if (this.isUpdating) {
+ return __('Updating');
}
- return s__('ClusterIntegration|Upgraded');
+ return __('Updated');
},
- upgradeFailureDescription() {
+ updateFailureDescription() {
return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
},
- upgradeSuccessDescription() {
- return sprintf(s__('ClusterIntegration|%{title} upgraded successfully.'), {
+ updateSuccessDescription() {
+ return sprintf(s__('ClusterIntegration|%{title} updated successfully.'), {
title: this.title,
});
},
- upgradeButtonLabel() {
+ updateButtonLabel() {
let label;
- if (this.upgradeAvailable && !this.updateFailed && !this.isUpgrading) {
- label = s__('ClusterIntegration|Upgrade');
- } else if (this.isUpgrading) {
- label = s__('ClusterIntegration|Updating');
+ if (this.updateAvailable && !this.updateFailed && !this.isUpdating) {
+ label = __('Update');
+ } else if (this.isUpdating) {
+ label = __('Updating');
} else if (this.updateFailed) {
- label = s__('ClusterIntegration|Retry update');
+ label = __('Retry update');
}
return label;
},
- isUpgrading() {
+ isUpdating() {
// Since upgrading is handled asynchronously on the backend we need this check to prevent any delay on the frontend
return this.status === APPLICATION_STATUS.UPDATING;
},
- shouldShowUpgradeDetails() {
+ shouldShowUpdateDetails() {
// This method only returns true when;
- // Upgrade was successful OR Upgrade failed
- // AND new upgrade is unavailable AND version information is present.
- return (this.updateSuccessful || this.updateFailed) && !this.upgradeAvailable && this.version;
+ // Update was successful OR Update failed
+ // AND new update is unavailable AND version information is present.
+ return (this.updateSuccessful || this.updateFailed) && !this.updateAvailable && this.version;
},
uninstallSuccessDescription() {
return sprintf(s__('ClusterIntegration|%{title} uninstalled successfully.'), {
@@ -253,7 +248,7 @@ export default {
watch: {
updateSuccessful(updateSuccessful) {
if (updateSuccessful) {
- this.$toast.show(this.upgradeSuccessDescription);
+ this.$toast.show(this.updateSuccessDescription);
}
},
uninstallSuccessful(uninstallSuccessful) {
@@ -269,8 +264,8 @@ export default {
params: this.installApplicationRequestParams,
});
},
- upgradeClicked() {
- eventHub.$emit('upgradeApplication', {
+ updateClicked() {
+ eventHub.$emit('updateApplication', {
id: this.id,
params: this.installApplicationRequestParams,
});
@@ -332,8 +327,8 @@ export default {
<div v-if="updateable">
<div
- v-if="shouldShowUpgradeDetails"
- class="form-text text-muted label p-0 js-cluster-application-upgrade-details"
+ v-if="shouldShowUpdateDetails"
+ class="form-text text-muted label p-0 js-cluster-application-update-details"
>
{{ versionLabel }}
<span v-if="updateSuccessful">to</span>
@@ -342,24 +337,24 @@ export default {
v-if="updateSuccessful"
:href="chartRepo"
target="_blank"
- class="js-cluster-application-upgrade-version"
+ class="js-cluster-application-update-version"
>chart v{{ version }}</gl-link
>
</div>
<div
- v-if="updateFailed && !isUpgrading"
- class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-upgrade-failure-message"
+ v-if="updateFailed && !isUpdating"
+ class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-update-details"
>
- {{ upgradeFailureDescription }}
+ {{ updateFailureDescription }}
</div>
<loading-button
- v-if="upgradeAvailable || updateFailed || isUpgrading"
- class="btn btn-primary js-cluster-application-upgrade-button mt-2"
- :loading="isUpgrading"
- :disabled="isUpgrading"
- :label="upgradeButtonLabel"
- @click="upgradeClicked"
+ v-if="updateAvailable || updateFailed || isUpdating"
+ class="btn btn-primary js-cluster-application-update-button mt-2"
+ :loading="isUpdating"
+ :disabled="isUpdating"
+ :label="updateButtonLabel"
+ @click="updateClicked"
/>
</div>
</div>
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 2d129245d37..970f5a7b297 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -376,7 +376,7 @@ export default {
:request-reason="applications.runner.requestReason"
:version="applications.runner.version"
:chart-repo="applications.runner.chartRepo"
- :upgrade-available="applications.runner.upgradeAvailable"
+ :update-available="applications.runner.updateAvailable"
:installed="applications.runner.installed"
:install-failed="applications.runner.installFailed"
:update-successful="applications.runner.updateSuccessful"
diff --git a/app/assets/javascripts/clusters/services/application_state_machine.js b/app/assets/javascripts/clusters/services/application_state_machine.js
index 14b80a116a7..17ea4d77795 100644
--- a/app/assets/javascripts/clusters/services/application_state_machine.js
+++ b/app/assets/javascripts/clusters/services/application_state_machine.js
@@ -123,7 +123,6 @@ const applicationStateMachine = {
target: INSTALLED,
effects: {
updateSuccessful: true,
- updateAcknowledged: false,
},
},
[UPDATE_ERRORED]: {
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 89e61c10a46..f64f0ca616f 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -56,8 +56,7 @@ export default class ClusterStore {
title: s__('ClusterIntegration|GitLab Runner'),
version: null,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
- upgradeAvailable: null,
- updateAcknowledged: true,
+ updateAvailable: null,
updateSuccessful: false,
updateFailed: false,
},
@@ -136,10 +135,6 @@ export default class ClusterStore {
this.state.applications[appId] = transitionApplicationState(currentAppState, event);
}
- acknowledgeSuccessfulUpdate(appId) {
- this.state.applications[appId].updateAcknowledged = true;
- }
-
updateAppProperty(appId, prop, value) {
this.state.applications[appId][prop] = value;
}
@@ -154,7 +149,7 @@ export default class ClusterStore {
status,
status_reason: statusReason,
version,
- update_available: upgradeAvailable,
+ update_available: updateAvailable,
can_uninstall: uninstallable,
} = serverAppEntry;
const currentApplicationState = this.state.applications[appId] || {};
@@ -191,7 +186,7 @@ export default class ClusterStore {
serverAppEntry.external_hostname || this.state.applications.knative.externalHostname;
} else if (appId === RUNNER) {
this.state.applications.runner.version = version;
- this.state.applications.runner.upgradeAvailable = upgradeAvailable;
+ this.state.applications.runner.updateAvailable = updateAvailable;
}
});
}
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index 2b3d6d1a3fa..d59b1136677 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -8,6 +8,7 @@ import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_d
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import NoteForm from '../../notes/components/note_form.vue';
import ImageDiffOverlay from './image_diff_overlay.vue';
import DiffDiscussions from './diff_discussions.vue';
@@ -26,6 +27,7 @@ export default {
ImageDiffOverlay,
NotDiffableViewer,
NoPreviewViewer,
+ userAvatarLink,
DiffFileDrafts: () => import('ee_component/batch_comments/components/diff_file_drafts.vue'),
},
mixins: [diffLineNoteFormMixin, draftCommentsMixin],
@@ -47,7 +49,7 @@ export default {
}),
...mapGetters('diffs', ['isInlineView', 'isParallelView']),
...mapGetters('diffs', ['getCommentFormForDiffFile']),
- ...mapGetters(['getNoteableData', 'noteableType']),
+ ...mapGetters(['getNoteableData', 'noteableType', 'getUserData']),
diffMode() {
return getDiffMode(this.diffFile);
},
@@ -72,6 +74,9 @@ export default {
diffFileHash() {
return this.diffFile.file_hash;
},
+ author() {
+ return this.getUserData;
+ },
},
methods: {
...mapActions('diffs', ['saveDiffDiscussion', 'closeDiffFileCommentForm']),
@@ -134,6 +139,14 @@ export default {
:can-comment="getNoteableData.current_user.can_create_note"
/>
<div v-if="showNotesContainer" class="note-container">
+ <user-avatar-link
+ v-if="diffFileCommentForm && author"
+ :link-href="author.path"
+ :img-src="author.avatar_url"
+ :img-alt="author.name"
+ :img-size="40"
+ class="d-none d-sm-block new-comment"
+ />
<diff-discussions
v-if="diffFile.discussions.length"
class="diff-file-discussions"
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 41670b45798..c209b857652 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -4,11 +4,13 @@ import { s__ } from '~/locale';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import noteForm from '../../notes/components/note_form.vue';
import autosave from '../../notes/mixins/autosave';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { DIFF_NOTE_TYPE } from '../constants';
export default {
components: {
noteForm,
+ userAvatarLink,
},
mixins: [autosave, diffLineNoteFormMixin],
props: {
@@ -41,7 +43,16 @@ export default {
diffViewType: state => state.diffs.diffViewType,
}),
...mapGetters('diffs', ['getDiffFileByHash']),
- ...mapGetters(['isLoggedIn', 'noteableType', 'getNoteableData', 'getNotesDataByProp']),
+ ...mapGetters([
+ 'isLoggedIn',
+ 'noteableType',
+ 'getNoteableData',
+ 'getNotesDataByProp',
+ 'getUserData',
+ ]),
+ author() {
+ return this.getUserData;
+ },
formData() {
return {
noteableData: this.noteableData,
@@ -99,6 +110,14 @@ export default {
<template>
<div class="content discussion-form discussion-form-container discussion-notes">
+ <user-avatar-link
+ v-if="author"
+ :link-href="author.path"
+ :img-src="author.avatar_url"
+ :img-alt="author.name"
+ :img-size="40"
+ class="d-none d-sm-block"
+ />
<note-form
ref="noteForm"
:is-editing="true"
diff --git a/app/assets/javascripts/emoji/no_emoji_validator.js b/app/assets/javascripts/emoji/no_emoji_validator.js
index 0fd4dd74953..384d62a133a 100644
--- a/app/assets/javascripts/emoji/no_emoji_validator.js
+++ b/app/assets/javascripts/emoji/no_emoji_validator.js
@@ -1,10 +1,11 @@
import { __ } from '~/locale';
import emojiRegex from 'emoji-regex';
+import InputValidator from '../validators/input_validator';
-const invalidInputClass = 'gl-field-error-outline';
-
-export default class NoEmojiValidator {
+export default class NoEmojiValidator extends InputValidator {
constructor(opts = {}) {
+ super();
+
const container = opts.container || '';
this.noEmojiEmelents = document.querySelectorAll(`${container} .js-block-emoji`);
@@ -19,45 +20,14 @@ export default class NoEmojiValidator {
const { value } = this.inputDomElement;
+ this.errorMessage = __('Invalid input, please avoid emojis');
+
this.validatePattern(value);
this.setValidationStateAndMessage();
}
validatePattern(value) {
const pattern = emojiRegex();
- this.hasEmojis = new RegExp(pattern).test(value);
-
- if (this.hasEmojis) {
- this.inputDomElement.setCustomValidity(__('Invalid input, please avoid emojis'));
- } else {
- this.inputDomElement.setCustomValidity('');
- }
- }
-
- setValidationStateAndMessage() {
- if (!this.inputDomElement.checkValidity()) {
- this.setInvalidState();
- } else {
- this.clearFieldValidationState();
- }
- }
-
- clearFieldValidationState() {
- this.inputDomElement.classList.remove(invalidInputClass);
- this.inputErrorMessage.classList.add('hide');
- }
-
- setInvalidState() {
- this.inputDomElement.classList.add(invalidInputClass);
- this.setErrorMessage();
- }
-
- setErrorMessage() {
- if (this.hasEmojis) {
- this.inputErrorMessage.innerHTML = this.inputDomElement.validationMessage;
- } else {
- this.inputErrorMessage.innerHTML = this.inputDomElement.title;
- }
- this.inputErrorMessage.classList.remove('hide');
+ this.invalidInput = new RegExp(pattern).test(value);
}
}
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index 1824a0f6147..685d8a6b245 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -1,23 +1,24 @@
<script>
import _ from 'underscore';
-import { mapActions, mapState, mapGetters, createNamespacedHelpers } from 'vuex';
+import { mapState, mapGetters, createNamespacedHelpers } from 'vuex';
import { sprintf, __ } from '~/locale';
import consts from '../../stores/modules/commit/constants';
import RadioGroup from './radio_group.vue';
+import NewMergeRequestOption from './new_merge_request_option.vue';
-const { mapState: mapCommitState, mapGetters: mapCommitGetters } = createNamespacedHelpers(
+const { mapState: mapCommitState, mapActions: mapCommitActions } = createNamespacedHelpers(
'commit',
);
export default {
components: {
RadioGroup,
+ NewMergeRequestOption,
},
computed: {
...mapState(['currentBranchId', 'changedFiles', 'stagedFiles']),
- ...mapCommitState(['commitAction', 'shouldCreateMR', 'shouldDisableNewMrOption']),
- ...mapGetters(['currentProject', 'currentBranch', 'currentMergeRequest']),
- ...mapCommitGetters(['shouldDisableNewMrOption']),
+ ...mapCommitState(['commitAction']),
+ ...mapGetters(['currentBranch']),
commitToCurrentBranchText() {
return sprintf(
__('Commit to %{branchName} branch'),
@@ -25,12 +26,12 @@ export default {
false,
);
},
- disableMergeRequestRadio() {
+ containsStagedChanges() {
return this.changedFiles.length > 0 && this.stagedFiles.length > 0;
},
},
watch: {
- disableMergeRequestRadio() {
+ containsStagedChanges() {
this.updateSelectedCommitAction();
},
},
@@ -38,11 +39,11 @@ export default {
this.updateSelectedCommitAction();
},
methods: {
- ...mapActions('commit', ['updateCommitAction', 'toggleShouldCreateMR']),
+ ...mapCommitActions(['updateCommitAction']),
updateSelectedCommitAction() {
if (this.currentBranch && !this.currentBranch.can_push) {
this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH);
- } else if (this.disableMergeRequestRadio) {
+ } else if (this.containsStagedChanges) {
this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH);
}
},
@@ -56,7 +57,7 @@ export default {
</script>
<template>
- <div class="append-bottom-15 ide-commit-radios">
+ <div class="append-bottom-15 ide-commit-options">
<radio-group
:value="$options.commitToCurrentBranch"
:disabled="currentBranch && !currentBranch.can_push"
@@ -69,17 +70,6 @@ export default {
:label="__('Create a new branch')"
:show-input="true"
/>
- <hr class="my-2" />
- <label class="mb-0">
- <input
- :checked="shouldCreateMR"
- :disabled="shouldDisableNewMrOption"
- type="checkbox"
- @change="toggleShouldCreateMR"
- />
- <span class="prepend-left-10" :class="{ 'text-secondary': shouldDisableNewMrOption }">
- {{ __('Start a new merge request') }}
- </span>
- </label>
+ <new-merge-request-option />
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
new file mode 100644
index 00000000000..b2e7b15089c
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
@@ -0,0 +1,43 @@
+<script>
+import { mapGetters, createNamespacedHelpers } from 'vuex';
+
+const {
+ mapState: mapCommitState,
+ mapGetters: mapCommitGetters,
+ mapActions: mapCommitActions,
+} = createNamespacedHelpers('commit');
+
+export default {
+ computed: {
+ ...mapCommitState(['shouldCreateMR']),
+ ...mapCommitGetters(['isCommittingToCurrentBranch', 'isCommittingToDefaultBranch']),
+ ...mapGetters(['hasMergeRequest', 'isOnDefaultBranch']),
+ currentBranchHasMr() {
+ return this.hasMergeRequest && this.isCommittingToCurrentBranch;
+ },
+ showNewMrOption() {
+ return (
+ this.isCommittingToDefaultBranch || !this.currentBranchHasMr || this.isCommittingToNewBranch
+ );
+ },
+ },
+ mounted() {
+ this.setShouldCreateMR();
+ },
+ methods: {
+ ...mapCommitActions(['toggleShouldCreateMR', 'setShouldCreateMR']),
+ },
+};
+</script>
+
+<template>
+ <div v-if="showNewMrOption">
+ <hr class="my-2" />
+ <label class="mb-0">
+ <input :checked="shouldCreateMR" type="checkbox" @change="toggleShouldCreateMR" />
+ <span class="prepend-left-10">
+ {{ __('Start a new merge request') }}
+ </span>
+ </label>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index e15b2a6f76b..b0c4969c5e4 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -125,6 +125,7 @@ export default {
'setFileEOL',
'updateViewer',
'removePendingTab',
+ 'triggerFilesChange',
]),
initEditor() {
if (this.shouldHideEditor) return;
@@ -256,6 +257,7 @@ export default {
'is-added': file.tempFile,
}"
class="multi-file-editor-holder"
+ @focusout="triggerFilesChange"
></div>
<content-viewer
v-if="showContentViewer"
diff --git a/app/assets/javascripts/ide/lib/keymap.json b/app/assets/javascripts/ide/lib/keymap.json
index 131abfebbed..2db87c07dde 100644
--- a/app/assets/javascripts/ide/lib/keymap.json
+++ b/app/assets/javascripts/ide/lib/keymap.json
@@ -7,5 +7,13 @@
"name": "toggleFileFinder",
"params": true
}
+ },
+ {
+ "id": "save-files",
+ "label": "Save files",
+ "bindings": ["CtrlCmd+KEY_S"],
+ "action": {
+ "name": "triggerFilesChange"
+ }
}
]
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index dc8ca732879..5429b834708 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -99,6 +99,7 @@ export const createTempEntry = (
commit(types.TOGGLE_FILE_OPEN, file.path);
commit(types.ADD_FILE_TO_CHANGED, file.path);
dispatch('setFileActive', file.path);
+ dispatch('triggerFilesChange');
}
if (parentPath && !state.entries[parentPath].opened) {
@@ -210,6 +211,8 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => {
if (entry.parentPath && state.entries[entry.parentPath].tree.length === 0) {
dispatch('deleteEntry', entry.parentPath);
}
+
+ dispatch('triggerFilesChange');
};
export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
@@ -240,6 +243,8 @@ export const renameEntry = (
if (!entryPath && !entry.tempFile) {
dispatch('deleteEntry', path);
}
+
+ dispatch('triggerFilesChange');
};
export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) =>
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index e7e8ac6d80b..dc40a1fa6a2 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -265,3 +265,8 @@ export const removePendingTab = ({ commit }, file) => {
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
};
+
+export const triggerFilesChange = () => {
+ // Used in EE for file mirroring
+ eventHub.$emit('ide.files.change');
+};
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index 5a736805fdc..406903129db 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -97,7 +97,12 @@ export const lastCommit = (state, getters) => {
export const currentBranch = (state, getters) =>
getters.currentProject && getters.currentProject.branches[state.currentBranchId];
+export const branchName = (_state, getters) => getters.currentBranch && getters.currentBranch.name;
+
export const packageJson = state => state.entries[packageJsonPath];
+export const isOnDefaultBranch = (_state, getters) =>
+ getters.currentProject && getters.currentProject.default_branch === getters.branchName;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index 77ea2084877..51062f092ad 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -18,15 +18,34 @@ export const discardDraft = ({ commit }) => {
commit(types.UPDATE_COMMIT_MESSAGE, '');
};
-export const updateCommitAction = ({ commit, rootGetters }, commitAction) => {
+export const updateCommitAction = ({ commit, dispatch }, commitAction) => {
commit(types.UPDATE_COMMIT_ACTION, {
commitAction,
- currentMergeRequest: rootGetters.currentMergeRequest,
});
+ dispatch('setShouldCreateMR');
};
export const toggleShouldCreateMR = ({ commit }) => {
commit(types.TOGGLE_SHOULD_CREATE_MR);
+ commit(types.INTERACT_WITH_NEW_MR);
+};
+
+export const setShouldCreateMR = ({
+ commit,
+ getters,
+ rootGetters,
+ state: { interactedWithNewMR },
+}) => {
+ const committingToExistingMR =
+ getters.isCommittingToCurrentBranch &&
+ rootGetters.hasMergeRequest &&
+ !rootGetters.isOnDefaultBranch;
+
+ if ((getters.isCommittingToDefaultBranch && !interactedWithNewMR) || committingToExistingMR) {
+ commit(types.TOGGLE_SHOULD_CREATE_MR, false);
+ } else if (!interactedWithNewMR) {
+ commit(types.TOGGLE_SHOULD_CREATE_MR, true);
+ }
};
export const updateBranchName = ({ commit }, branchName) => {
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 6aa5d22a4ea..64779e9e4df 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -48,8 +48,11 @@ export const preBuiltCommitMessage = (state, _, rootState) => {
export const isCreatingNewBranch = state => state.commitAction === consts.COMMIT_TO_NEW_BRANCH;
-export const shouldDisableNewMrOption = (state, _getters, _rootState, rootGetters) =>
- rootGetters.currentMergeRequest && state.commitAction === consts.COMMIT_TO_CURRENT_BRANCH;
+export const isCommittingToCurrentBranch = state =>
+ state.commitAction === consts.COMMIT_TO_CURRENT_BRANCH;
+
+export const isCommittingToDefaultBranch = (_state, getters, _rootState, rootGetters) =>
+ getters.isCommittingToCurrentBranch && rootGetters.isOnDefaultBranch;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
index 7ad8f3570b7..b81918156b0 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
@@ -3,3 +3,4 @@ export const UPDATE_COMMIT_ACTION = 'UPDATE_COMMIT_ACTION';
export const UPDATE_NEW_BRANCH_NAME = 'UPDATE_NEW_BRANCH_NAME';
export const UPDATE_LOADING = 'UPDATE_LOADING';
export const TOGGLE_SHOULD_CREATE_MR = 'TOGGLE_SHOULD_CREATE_MR';
+export const INTERACT_WITH_NEW_MR = 'INTERACT_WITH_NEW_MR';
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutations.js b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
index be0f894c059..14957d283bb 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
@@ -1,5 +1,4 @@
import * as types from './mutation_types';
-import consts from './constants';
export default {
[types.UPDATE_COMMIT_MESSAGE](state, commitMessage) {
@@ -7,14 +6,8 @@ export default {
commitMessage,
});
},
- [types.UPDATE_COMMIT_ACTION](state, { commitAction, currentMergeRequest }) {
- Object.assign(state, {
- commitAction,
- shouldCreateMR:
- commitAction === consts.COMMIT_TO_CURRENT_BRANCH && currentMergeRequest
- ? false
- : state.shouldCreateMR,
- });
+ [types.UPDATE_COMMIT_ACTION](state, { commitAction }) {
+ Object.assign(state, { commitAction });
},
[types.UPDATE_NEW_BRANCH_NAME](state, newBranchName) {
Object.assign(state, {
@@ -26,9 +19,12 @@ export default {
submitCommitLoading,
});
},
- [types.TOGGLE_SHOULD_CREATE_MR](state) {
+ [types.TOGGLE_SHOULD_CREATE_MR](state, shouldCreateMR) {
Object.assign(state, {
- shouldCreateMR: !state.shouldCreateMR,
+ shouldCreateMR: shouldCreateMR === undefined ? !state.shouldCreateMR : shouldCreateMR,
});
},
+ [types.INTERACT_WITH_NEW_MR](state) {
+ Object.assign(state, { interactedWithNewMR: true });
+ },
};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/state.js b/app/assets/javascripts/ide/stores/modules/commit/state.js
index 5c0e6a41ca1..53647a7e3e3 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/state.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/state.js
@@ -4,4 +4,5 @@ export default () => ({
newBranchName: '',
submitCommitLoading: false,
shouldCreateMR: false,
+ interactedWithNewMR: false,
});
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index a24c71aeab1..28a7ebfdc69 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
@@ -51,6 +51,7 @@ export default class LinkedTabs {
this.defaultAction = this.options.defaultAction;
this.action = this.options.action || this.defaultAction;
+ this.hashedTabs = this.options.hashedTabs || false;
if (this.action === 'show') {
this.action = this.defaultAction;
@@ -58,6 +59,10 @@ export default class LinkedTabs {
this.currentLocation = window.location;
+ if (this.hashedTabs) {
+ this.action = this.currentLocation.hash || this.action;
+ }
+
const tabSelector = `${this.options.parentEl} a[data-toggle="tab"]`;
// since this is a custom event we need jQuery :(
@@ -91,7 +96,9 @@ export default class LinkedTabs {
copySource.replace(/\/+$/, '');
- const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
+ const newState = this.hashedTabs
+ ? copySource
+ : `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
window.history.replaceState(
{
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index d3e6851496b..d521c462ad8 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -81,7 +81,7 @@ export const getDayName = date =>
*/
export const formatDate = datetime => {
if (_.isString(datetime) && datetime.match(/\d+-\d+\d+ /)) {
- throw new Error('Invalid date');
+ throw new Error(__('Invalid date'));
}
return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
};
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index 9ddfb4bca11..61c8b8803d7 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -100,3 +100,9 @@ export function numberToHumanSize(size) {
* @returns {Float} The summed value
*/
export const sum = (a = 0, b = 0) => a + b;
+
+/**
+ * Checks if the provided number is odd
+ * @param {Int} number
+ */
+export const isOdd = (number = 0) => number % 2;
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index def810e879a..2314f7b80cf 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -106,10 +106,6 @@ export default {
type: String,
required: true,
},
- showTimeWindowDropdown: {
- type: Boolean,
- required: true,
- },
customMetricsAvailable: {
type: Boolean,
required: false,
@@ -172,7 +168,7 @@ export default {
if (!this.hasMetrics) {
this.setGettingStartedEmptyState();
} else {
- this.fetchData(getTimeDiff(this.timeWindows.eightHours));
+ this.fetchData(getTimeDiff(this.selectedTimeWindow));
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
@@ -248,7 +244,7 @@ export default {
>
</gl-dropdown>
</div>
- <div v-if="showTimeWindowDropdown" class="d-flex align-items-center prepend-left-8">
+ <div class="d-flex align-items-center prepend-left-8">
<strong>{{ s__('Metrics|Show last') }}</strong>
<gl-dropdown
class="prepend-left-10 js-time-window-dropdown"
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 57771ccf4d9..62c0f44c1e6 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -16,7 +16,6 @@ export default (props = {}) => {
props: {
...el.dataset,
hasMetrics: parseBoolean(el.dataset.hasMetrics),
- showTimeWindowDropdown: gon.features.metricsTimeWindow,
...props,
},
});
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 1c9ca180100..a7156bd2406 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -990,6 +990,14 @@ export default class Notes {
form.find('#note_position').val(dataHolder.attr('data-position'));
form
+ .prepend(
+ `<div class="avatar-note-form-holder"><div class="content"><a href="${escape(
+ gon.current_username,
+ )}" class="user-avatar-link d-none d-sm-block"><img class="avatar s40" src="${encodeURI(
+ gon.current_user_avatar_url,
+ )}" alt="${escape(gon.current_user_fullname)}" /></a></div></div>`,
+ )
+ .append('</div>')
.find('.js-close-discussion-note-form')
.show()
.removeClass('hide');
@@ -1025,6 +1033,9 @@ export default class Notes {
target: $link,
lineType: link.dataset.lineType,
showReplyInput,
+ currentUsername: gon.current_username,
+ currentUserAvatar: gon.current_user_avatar_url,
+ currentUserFullname: gon.current_user_fullname,
});
}
@@ -1053,7 +1064,15 @@ export default class Notes {
this.setupDiscussionNoteForm($link, newForm);
}
- toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) {
+ toggleDiffNote({
+ target,
+ lineType,
+ forceShow,
+ showReplyInput = false,
+ currentUsername,
+ currentUserAvatar,
+ currentUserFullname,
+ }) {
var $link,
addForm,
hasNotes,
@@ -1546,7 +1565,9 @@ export default class Notes {
<div class="note-header">
<div class="note-header-info">
<a href="/${_.escape(currentUsername)}">
- <span class="d-none d-sm-inline-block">${_.escape(currentUsername)}</span>
+ <span class="d-none d-sm-inline-block bold">${_.escape(
+ currentUsername,
+ )}</span>
<span class="note-headline-light">${_.escape(currentUsername)}</span>
</a>
</div>
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 688c06878ac..075c28e8d07 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -337,6 +337,8 @@ Please check your network connection and try again.`;
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
+ :locked-issue-docs-path="lockedIssueDocsPath"
+ :confidential-issue-docs-path="confidentialIssueDocsPath"
/>
<markdown-field
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 307e56708e0..efd84f5722c 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -49,8 +49,8 @@ export default {
</script>
<template>
- <div v-if="resolvableDiscussionsCount > 0" class="line-resolve-all-container prepend-top-8">
- <div>
+ <div v-if="resolvableDiscussionsCount > 0" class="line-resolve-all-container full-width-mobile">
+ <div class="full-width-mobile d-flex d-sm-block">
<div :class="{ 'has-next-btn': hasNextButton }" class="line-resolve-all">
<span
:class="{ 'is-active': allResolved }"
@@ -64,7 +64,11 @@ export default {
{{ n__('discussion resolved', 'discussions resolved', resolvableDiscussionsCount) }}
</span>
</div>
- <div v-if="resolveAllDiscussionsIssuePath && !allResolved" class="btn-group" role="group">
+ <div
+ v-if="resolveAllDiscussionsIssuePath && !allResolved"
+ class="btn-group btn-group-sm"
+ role="group"
+ >
<a
v-gl-tooltip
:href="resolveAllDiscussionsIssuePath"
@@ -74,7 +78,7 @@ export default {
<icon name="issue-new" />
</a>
</div>
- <div v-if="isLoggedIn && !allResolved" class="btn-group" role="group">
+ <div v-if="isLoggedIn && !allResolved" class="btn-group btn-group-sm" role="group">
<button
v-gl-tooltip
title="Jump to first unresolved discussion"
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 47951591e82..eb3fbbe1385 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -105,12 +105,12 @@ export default {
<template>
<div
v-if="displayFilters"
- class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom"
+ class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom full-width-mobile"
>
<button
id="discussion-filter-dropdown"
ref="dropdownToggle"
- class="btn btn-default qa-discussion-filter"
+ class="btn btn-sm qa-discussion-filter"
data-toggle="dropdown"
aria-expanded="false"
>
diff --git a/app/assets/javascripts/notes/components/discussion_locked_widget.vue b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
index c469a6b7bcd..53f509185a8 100644
--- a/app/assets/javascripts/notes/components/discussion_locked_widget.vue
+++ b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
@@ -1,12 +1,24 @@
<script>
+import { GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
+import { __, sprintf } from '~/locale';
import Issuable from '~/vue_shared/mixins/issuable';
+import issuableStateMixin from '../mixins/issuable_state';
export default {
components: {
Icon,
+ GlLink,
+ },
+ mixins: [Issuable, issuableStateMixin],
+ computed: {
+ lockedIssueWarning() {
+ return sprintf(
+ __('This %{issuableDisplayName} is locked. Only project members can comment.'),
+ { issuableDisplayName: this.issuableDisplayName },
+ );
+ },
},
- mixins: [Issuable],
};
</script>
@@ -15,7 +27,11 @@ export default {
<span class="issuable-note-warning inline">
<icon :size="16" name="lock" class="icon" />
<span>
- This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.
+ {{ lockedIssueWarning }}
+
+ <gl-link :href="lockedIssueDocsPath" target="_blank" class="learn-more">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
</span>
</div>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index c9c40cb6acf..844d0c3e376 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -195,7 +195,7 @@ export default {
</button>
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse">
- <a :href="reportAbusePath">{{ __('Report abuse to GitLab') }}</a>
+ <a :href="reportAbusePath">{{ __('Report abuse to admin') }}</a>
</li>
<li v-if="noteUrl">
<button
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index acbb91ce7be..09ecb695214 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -234,6 +234,8 @@ export default {
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
+ :locked-issue-docs-path="lockedIssueDocsPath"
+ :confidential-issue-docs-path="confidentialIssueDocsPath"
/>
<markdown-field
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 5c59c0c32dd..fbf82fab9e9 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -82,7 +82,7 @@ export default {
:data-username="author.username"
>
<slot name="note-header-info"></slot>
- <span class="note-header-author-name">{{ author.name }}</span>
+ <span class="note-header-author-name bold">{{ author.name }}</span>
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
<span class="note-headline-light">@{{ author.username }}</span>
</a>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 2c549e7abdd..eb6a4a67fff 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -87,7 +87,11 @@ export default {
'unresolvedDiscussionsCount',
'hasUnresolvedDiscussions',
'showJumpToNextDiscussion',
+ 'getUserData',
]),
+ currentUser() {
+ return this.getUserData;
+ },
author() {
return this.firstNote.author;
},
@@ -377,6 +381,14 @@ Please check your network connection and try again.`;
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder"
>
+ <user-avatar-link
+ v-if="!isReplying && currentUser"
+ :link-href="currentUser.path"
+ :img-src="currentUser.avatar_url"
+ :img-alt="currentUser.name"
+ :img-size="40"
+ class="d-none d-sm-block"
+ />
<discussion-actions
v-if="!isReplying && userCanReply"
:discussion="discussion"
@@ -388,18 +400,27 @@ Please check your network connection and try again.`;
@resolve="resolveHandler"
@jumpToNextDiscussion="jumpToNextDiscussion"
/>
- <note-form
- v-if="isReplying"
- ref="noteForm"
- :discussion="discussion"
- :is-editing="false"
- :line="diffLine"
- save-button-title="Comment"
- :autosave-key="autosaveKey"
- @handleFormUpdateAddToReview="addReplyToReview"
- @handleFormUpdate="saveReply"
- @cancelForm="cancelReplyForm"
- />
+ <div v-if="isReplying" class="avatar-note-form-holder">
+ <user-avatar-link
+ v-if="currentUser"
+ :link-href="currentUser.path"
+ :img-src="currentUser.avatar_url"
+ :img-alt="currentUser.name"
+ :img-size="40"
+ class="d-none d-sm-block"
+ />
+ <note-form
+ ref="noteForm"
+ :discussion="discussion"
+ :is-editing="false"
+ :line="diffLine"
+ save-button-title="Comment"
+ :autosave-key="autosaveKey"
+ @handleFormUpdateAddToReview="addReplyToReview"
+ @handleFormUpdate="saveReply"
+ @cancelForm="cancelReplyForm"
+ />
+ </div>
<note-signed-out-widget v-if="!userCanReply" />
</div>
</template>
diff --git a/app/assets/javascripts/notes/mixins/issuable_state.js b/app/assets/javascripts/notes/mixins/issuable_state.js
index ded0ac3cfa9..d97d9f6850a 100644
--- a/app/assets/javascripts/notes/mixins/issuable_state.js
+++ b/app/assets/javascripts/notes/mixins/issuable_state.js
@@ -1,4 +1,15 @@
+import { mapGetters } from 'vuex';
+
export default {
+ computed: {
+ ...mapGetters(['getNoteableDataByProp']),
+ lockedIssueDocsPath() {
+ return this.getNoteableDataByProp('locked_discussion_docs_path');
+ },
+ confidentialIssueDocsPath() {
+ return this.getNoteableDataByProp('confidential_issues_docs_path');
+ },
+ },
methods: {
isConfidential(issue) {
return Boolean(issue.confidential);
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index ae0a8c74964..8a5300c9266 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -12,5 +12,6 @@ document.addEventListener('DOMContentLoaded', () => {
saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint,
+ maskableRegex: variableListEl.dataset.maskableRegex,
});
});
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index b288989b252..f0d529758d5 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -39,6 +39,11 @@ export default class Project {
$label.text(activeText);
});
+ $('#modal-geo-info').data({
+ cloneUrlSecondary: $this.attr('href'),
+ cloneUrlPrimary: $this.data('primaryUrl') || '',
+ });
+
if (mobileCloneField) {
mobileCloneField.dataset.clipboardText = url;
} else {
@@ -67,6 +72,13 @@ export default class Project {
.remove();
return e.preventDefault();
});
+ $('.hide-shared-runner-limit-message').on('click', function(e) {
+ var $alert = $(this).parents('.shared-runner-quota-message');
+ var scope = $alert.data('scope');
+ Cookies.set('hide_shared_runner_quota_message', 'false', { path: scope });
+ $alert.remove();
+ e.preventDefault();
+ });
$('.hide-auto-devops-implicitly-enabled-banner').on('click', function(e) {
const projectId = $(this).data('project-id');
const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`;
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 15c6fb550c1..885247335a4 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -21,6 +21,7 @@ document.addEventListener('DOMContentLoaded', () => {
saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint,
+ maskableRegex: variableListEl.dataset.maskableRegex,
});
// hide extra auto devops settings based checkbox state
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 19d9903c988..dea7c586868 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -175,11 +175,6 @@ export default {
if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true);
else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false);
},
-
- buildsAccessLevel(value, oldValue) {
- if (value === 0) toggleHiddenClassBySelector('.builds-feature', true);
- else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false);
- },
},
methods: {
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index e1a3f42a71f..3f5a3e15c2c 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import LengthValidator from './length_validator';
import UsernameValidator from './username_validator';
import NoEmojiValidator from '../../../emoji/no_emoji_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
@@ -6,6 +7,7 @@ import OAuthRememberMe from './oauth_remember_me';
import preserveUrlFragment from './preserve_url_fragment';
document.addEventListener('DOMContentLoaded', () => {
+ new LengthValidator(); // eslint-disable-line no-new
new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/sessions/new/length_validator.js b/app/assets/javascripts/pages/sessions/new/length_validator.js
new file mode 100644
index 00000000000..3d687ca08cc
--- /dev/null
+++ b/app/assets/javascripts/pages/sessions/new/length_validator.js
@@ -0,0 +1,32 @@
+import InputValidator from '../../../validators/input_validator';
+
+const errorMessageClass = 'gl-field-error';
+
+export default class LengthValidator extends InputValidator {
+ constructor(opts = {}) {
+ super();
+
+ const container = opts.container || '';
+ const validateLengthElements = document.querySelectorAll(`${container} .js-validate-length`);
+
+ validateLengthElements.forEach(element =>
+ element.addEventListener('input', this.eventHandler.bind(this)),
+ );
+ }
+
+ eventHandler(event) {
+ this.inputDomElement = event.target;
+ this.inputErrorMessage = this.inputDomElement.parentElement.querySelector(
+ `.${errorMessageClass}`,
+ );
+
+ const { value } = this.inputDomElement;
+ const { maxLengthMessage, maxLength } = this.inputDomElement.dataset;
+
+ this.errorMessage = maxLengthMessage;
+
+ this.invalidInput = value.length > parseInt(maxLength, 10);
+
+ this.setValidationStateAndMessage();
+ }
+}
diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue
index 50f2910e02d..ee07efea3b0 100644
--- a/app/assets/javascripts/reports/components/issues_list.vue
+++ b/app/assets/javascripts/reports/components/issues_list.vue
@@ -52,11 +52,21 @@ export default {
required: false,
default: '',
},
- showReportSectionStatus: {
+ showReportSectionStatusIcon: {
type: Boolean,
required: false,
default: true,
},
+ issuesUlElementClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ issueItemClass: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
issuesWithState() {
@@ -67,6 +77,9 @@ export default {
...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)),
];
},
+ wclass() {
+ return `report-block-list ${this.issuesUlElementClass}`;
+ },
},
};
</script>
@@ -77,7 +90,7 @@ export default {
:size="$options.typicalReportItemHeight"
class="report-block-container"
wtag="ul"
- wclass="report-block-list"
+ :wclass="wclass"
>
<report-item
v-for="(wrapped, index) in issuesWithState"
@@ -86,7 +99,8 @@ export default {
:status="wrapped.status"
:component="component"
:is-new="wrapped.isNew"
- :show-report-section-status="showReportSectionStatus"
+ :show-report-section-status-icon="showReportSectionStatusIcon"
+ :class="issueItemClass"
/>
</smart-virtual-list>
</template>
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 241185e3126..3d576caaf8f 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -3,10 +3,7 @@ import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import Popover from '~/vue_shared/components/help_popover.vue';
import IssuesList from './issues_list.vue';
-
-const LOADING = 'LOADING';
-const ERROR = 'ERROR';
-const SUCCESS = 'SUCCESS';
+import { status } from '../constants';
export default {
name: 'ReportSection',
@@ -42,7 +39,8 @@ export default {
},
successText: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
unresolvedIssues: {
type: Array,
@@ -78,6 +76,21 @@ export default {
required: false,
default: true,
},
+ issuesUlElementClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ issuesListContainerClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ issueItemClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
},
data() {
@@ -91,13 +104,13 @@ export default {
return this.isCollapsed ? __('Expand') : __('Collapse');
},
isLoading() {
- return this.status === LOADING;
+ return this.status === status.LOADING;
},
loadingFailed() {
- return this.status === ERROR;
+ return this.status === status.ERROR;
},
isSuccess() {
- return this.status === SUCCESS;
+ return this.status === status.SUCCESS;
},
isCollapsible() {
return !this.alwaysOpen && this.hasIssues;
@@ -132,6 +145,15 @@ export default {
hasPopover() {
return Object.keys(this.popoverOptions).length > 0;
},
+ slotName() {
+ if (this.isSuccess) {
+ return 'success';
+ } else if (this.isLoading) {
+ return 'loading';
+ }
+
+ return 'error';
+ },
},
methods: {
toggleCollapsed() {
@@ -147,6 +169,7 @@ export default {
<div class="media-body d-flex flex-align-self-center">
<span class="js-code-text code-text">
{{ headerText }}
+ <slot :name="slotName"></slot>
<popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" />
</span>
@@ -172,6 +195,9 @@ export default {
:neutral-issues="neutralIssues"
:component="component"
:show-report-section-status-icon="showReportSectionStatusIcon"
+ :issues-ul-element-class="issuesUlElementClass"
+ :class="issuesListContainerClass"
+ :issue-item-class="issueItemClass"
/>
</slot>
</div>
diff --git a/app/assets/javascripts/reports/constants.js b/app/assets/javascripts/reports/constants.js
index c323dc543f3..66ac1af062b 100644
--- a/app/assets/javascripts/reports/constants.js
+++ b/app/assets/javascripts/reports/constants.js
@@ -16,3 +16,9 @@ export const STATUS_NEUTRAL = 'neutral';
export const ICON_WARNING = 'warning';
export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound';
+
+export const status = {
+ LOADING: 'LOADING',
+ ERROR: 'ERROR',
+ SUCCESS: 'SUCCESS',
+};
diff --git a/app/assets/javascripts/validators/input_validator.js b/app/assets/javascripts/validators/input_validator.js
new file mode 100644
index 00000000000..f37373977b8
--- /dev/null
+++ b/app/assets/javascripts/validators/input_validator.js
@@ -0,0 +1,34 @@
+const invalidInputClass = 'gl-field-error-outline';
+
+export default class InputValidator {
+ constructor() {
+ this.inputDomElement = {};
+ this.inputErrorMessage = {};
+ this.errorMessage = null;
+ this.invalidInput = null;
+ }
+
+ setValidationStateAndMessage() {
+ this.setValidationMessage();
+
+ const isInvalidInput = !this.inputDomElement.checkValidity();
+ this.inputDomElement.classList.toggle(invalidInputClass, isInvalidInput);
+ this.inputErrorMessage.classList.toggle('hide', !isInvalidInput);
+ }
+
+ setValidationMessage() {
+ if (this.invalidInput) {
+ this.inputDomElement.setCustomValidity(this.errorMessage);
+ this.inputErrorMessage.innerHTML = this.errorMessage;
+ } else {
+ this.resetValidationMessage();
+ }
+ }
+
+ resetValidationMessage() {
+ if (this.inputDomElement.validationMessage === this.errorMessage) {
+ this.inputDomElement.setCustomValidity('');
+ this.inputErrorMessage.innerHTML = this.inputDomElement.title;
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue b/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
new file mode 100644
index 00000000000..7d49c87271d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
@@ -0,0 +1,89 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import Icon from './icon.vue';
+
+export default {
+ components: {
+ Icon,
+ GlButton,
+ },
+ props: {
+ size: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ primaryButtonClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ dropdownClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ actions: {
+ type: Array,
+ required: true,
+ },
+ defaultAction: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ selectedAction: this.defaultAction,
+ };
+ },
+ computed: {
+ selectedActionTitle() {
+ return this.actions[this.selectedAction].title;
+ },
+ buttonSizeClass() {
+ return `btn-${this.size}`;
+ },
+ },
+ methods: {
+ handlePrimaryActionClick() {
+ this.$emit('onActionClick', this.actions[this.selectedAction]);
+ },
+ handleActionClick(selectedAction) {
+ this.selectedAction = selectedAction;
+ this.$emit('onActionSelect', selectedAction);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="btn-group droplab-dropdown comment-type-dropdown">
+ <gl-button :class="primaryButtonClass" :size="size" @click.prevent="handlePrimaryActionClick">
+ {{ selectedActionTitle }}
+ </gl-button>
+ <button
+ :class="buttonSizeClass"
+ type="button"
+ class="btn dropdown-toggle pl-2 pr-2"
+ data-display="static"
+ data-toggle="dropdown"
+ >
+ <icon name="arrow-down" aria-label="toggle dropdown" />
+ </button>
+ <ul :class="dropdownClass" class="dropdown-menu dropdown-open-top">
+ <template v-for="(action, index) in actions">
+ <li :key="index" :class="{ 'droplab-item-selected': selectedAction === index }">
+ <gl-button class="btn-transparent" @click.prevent="handleActionClick(index)">
+ <i aria-hidden="true" class="fa fa-check icon"> </i>
+ <div class="description">
+ <strong>{{ action.title }}</strong>
+ <p>{{ action.description }}</p>
+ </div>
+ </gl-button>
+ </li>
+ <li v-if="index === 0" :key="`${index}-separator`" class="divider droplab-item-ignore"></li>
+ </template>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
index 7e79e63aa1e..715cf97f0ac 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
@@ -62,6 +62,15 @@ export default {
assigneeName: assignee.name,
});
},
+ // This method is for backward compat
+ // since Graph query would return camelCase
+ // props while Rails would return snake_case
+ webUrl(assignee) {
+ return assignee.web_url || assignee.webUrl;
+ },
+ avatarUrl(assignee) {
+ return assignee.avatar_url || assignee.avatarUrl;
+ },
},
};
</script>
@@ -70,9 +79,9 @@ export default {
<user-avatar-link
v-for="assignee in assigneesToShow"
:key="assignee.id"
- :link-href="assignee.web_url"
+ :link-href="webUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
- :img-src="assignee.avatar_url"
+ :img-src="avatarUrl(assignee)"
:img-size="24"
class="js-no-trigger"
tooltip-placement="bottom"
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
index 53e6efa6ea3..9b2ee5062b1 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
@@ -19,10 +19,14 @@ export default {
},
computed: {
milestoneDue() {
- return this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null;
+ const dueDate = this.milestone.due_date || this.milestone.dueDate;
+
+ return dueDate ? parsePikadayDate(dueDate) : null;
},
milestoneStart() {
- return this.milestone.start_date ? parsePikadayDate(this.milestone.start_date) : null;
+ const startDate = this.milestone.start_date || this.milestone.startDate;
+
+ return startDate ? parsePikadayDate(startDate) : null;
},
isMilestoneStarted() {
if (!this.milestoneStart) {
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
index e92babc499b..e438ff16a41 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
@@ -1,9 +1,17 @@
<script>
+import { GlLink } from '@gitlab/ui';
+import _ from 'underscore';
+import { sprintf } from '~/locale';
import icon from '../../../vue_shared/components/icon.vue';
+function buildDocsLinkStart(path) {
+ return `<a href="${_.escape(path)}" target="_blank" rel="noopener noreferrer">`;
+}
+
export default {
components: {
icon,
+ GlLink,
},
props: {
isLocked: {
@@ -16,6 +24,16 @@ export default {
default: false,
required: false,
},
+ lockedIssueDocsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ confidentialIssueDocsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
warningIcon() {
@@ -27,6 +45,17 @@ export default {
isLockedAndConfidential() {
return this.isConfidential && this.isLocked;
},
+ confidentialAndLockedDiscussionText() {
+ return sprintf(
+ 'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
+ {
+ confidentialLinkStart: buildDocsLinkStart(this.confidentialIssueDocsPath),
+ lockedLinkStart: buildDocsLinkStart(this.lockedIssueDocsPath),
+ linkEnd: '</a>',
+ },
+ false,
+ );
+ },
},
};
</script>
@@ -35,20 +64,26 @@ export default {
<icon v-if="!isLockedAndConfidential" :name="warningIcon" :size="16" class="icon inline" />
<span v-if="isLockedAndConfidential">
- {{ __('This issue is confidential and locked.') }}
+ <span v-html="confidentialAndLockedDiscussionText"></span>
{{
- __(`People without permission will never
-get a notification and won't be able to comment.`)
+ __(`People without permission will never get a notification and won't be able to comment.`)
}}
</span>
<span v-else-if="isConfidential">
{{ __('This is a confidential issue.') }}
- {{ __('Your comment will not be visible to the public.') }}
+ {{ __('People without permission will never get a notification.') }}
+ <gl-link :href="confidentialIssueDocsPath" target="_blank">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
<span v-else-if="isLocked">
- {{ __('This issue is locked.') }} {{ __('Only project members can comment.') }}
+ {{ __('This issue is locked.') }}
+ {{ __('Only project members can comment.') }}
+ <gl-link :href="lockedIssueDocsPath" target="_blank">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
index b807a35b421..05ad7710a62 100644
--- a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
@@ -24,6 +24,11 @@ export default {
required: false,
default: false,
},
+ greyLinkWhenMerged: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
stateTitle() {
@@ -36,6 +41,11 @@ export default {
},
);
},
+ issueableLinkClass() {
+ return this.greyLinkWhenMerged
+ ? `sortable-link ${this.state === 'merged' ? ' text-secondary' : ''}`
+ : 'sortable-link';
+ },
},
};
</script>
@@ -69,7 +79,7 @@ export default {
class="confidential-icon append-right-4 align-self-baseline align-self-md-auto mt-xl-0"
:aria-label="__('Confidential')"
/>
- <a :href="computedPath" class="sortable-link">{{ title }}</a>
+ <a :href="computedPath" :class="issueableLinkClass">{{ title }}</a>
</div>
<div class="item-meta d-flex flex-wrap mt-xl-0 justify-content-xl-end flex-xl-nowrap">
<div
diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
new file mode 100644
index 00000000000..bf59a6abf3f
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
@@ -0,0 +1,121 @@
+<script>
+import $ from 'jquery';
+import { GlButton, GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import Clipboard from 'clipboard';
+
+export default {
+ components: {
+ GlButton,
+ Icon,
+ },
+
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+
+ props: {
+ text: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ container: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ modalId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ target: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+ tooltipContainer: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+
+ copySuccessText: __('Copied'),
+
+ computed: {
+ modalDomId() {
+ return this.modalId ? `#${this.modalId}` : '';
+ },
+ },
+
+ mounted() {
+ this.$nextTick(() => {
+ this.clipboard = new Clipboard(this.$el, {
+ container:
+ document.querySelector(`${this.modalDomId} div.modal-content`) ||
+ document.getElementById(this.container) ||
+ document.body,
+ });
+ this.clipboard
+ .on('success', e => {
+ this.updateTooltip(e.trigger);
+ this.$emit('success', e);
+ // Clear the selection and blur the trigger so it loses its border
+ e.clearSelection();
+ $(e.trigger).blur();
+ })
+ .on('error', e => this.$emit('error', e));
+ });
+ },
+
+ destroyed() {
+ if (this.clipboard) {
+ this.clipboard.destroy();
+ }
+ },
+
+ methods: {
+ updateTooltip(target) {
+ const $target = $(target);
+ const originalTitle = $target.data('originalTitle');
+
+ if ($target.tooltip) {
+ /**
+ * The original tooltip will continue staying there unless we remove it by hand.
+ * $target.tooltip('hide') isn't working.
+ */
+ $('.tooltip').remove();
+ $target.attr('title', this.$options.copySuccessText);
+ $target.tooltip('_fixTitle');
+ $target.tooltip('show');
+ $target.attr('title', originalTitle);
+ $target.tooltip('_fixTitle');
+ }
+ },
+ },
+};
+</script>
+<template>
+ <gl-button
+ v-gl-tooltip="{ placement: tooltipPlacement, container: tooltipContainer }"
+ :data-clipboard-target="target"
+ :data-clipboard-text="text"
+ :title="title"
+ >
+ <slot>
+ <icon name="duplicate" />
+ </slot>
+ </gl-button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
index a50f49c1279..baed26a157c 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -51,7 +51,7 @@ export default {
<div class="note-header">
<div class="note-header-info">
<a :href="getUserData.path">
- <span class="d-none d-sm-inline-block">{{ getUserData.name }}</span>
+ <span class="d-none d-sm-inline-block bold">{{ getUserData.name }}</span>
<span class="note-headline-light">@{{ getUserData.username }}</span>
</a>
</div>
diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js
deleted file mode 100644
index 2d2732d0661..00000000000
--- a/app/assets/javascripts/vue_shared/models/label.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default class ListLabel {
- constructor(obj) {
- this.id = obj.id;
- this.title = obj.title;
- this.type = obj.type;
- this.color = obj.color;
- this.textColor = obj.text_color;
- this.description = obj.description;
- this.priority = obj.priority !== null ? obj.priority : Infinity;
- }
-}
-
-window.ListLabel = ListLabel;
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
index d0aa6ec78aa..774be9ef588 100644
--- a/app/assets/stylesheets/components/popover.scss
+++ b/app/assets/stylesheets/components/popover.scss
@@ -39,6 +39,25 @@
}
}
+.onboarding-popover {
+ box-shadow: 0 2px 4px $dropdown-shadow-color;
+
+ .popover-body {
+ font-size: $gl-font-size;
+ line-height: $gl-line-height;
+ padding: $gl-padding;
+ }
+
+ .popover-header {
+ display: none;
+ }
+
+ .accept-mr-label {
+ background-color: $accepting-mr-label-color;
+ color: $white-light;
+ }
+}
+
.onboarding-welcome-page {
.popover {
min-width: auto;
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 257d788873c..6f5a2e561af 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -268,3 +268,27 @@ $skeleton-line-widths: (
@include webkit-prefix(animation-duration, 1s);
transform-origin: 50% 50%;
}
+
+/* ----------------------------------------------
+ * Generated by Animista on 2019-4-26 17:40:41
+ * w: http://animista.net, t: @cssanimista
+ * ---------------------------------------------- */
+@keyframes slide-in-fwd-bottom {
+ 0% {
+ transform: translateZ(-1400px) translateY(800px);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: translateZ(0) translateY(0);
+ opacity: 1;
+ }
+}
+
+.slide-in-fwd-bottom-enter-active {
+ animation: slide-in-fwd-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
+}
+
+.slide-in-fwd-bottom-leave-active {
+ animation: slide-in-fwd-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both reverse;
+}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 3aabb66f7a6..65c0ee74c60 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -199,6 +199,7 @@
&.user-cover-block {
padding: 24px 0 0;
+ border-bottom: 1px solid $border-color;
.nav-links {
width: 100%;
@@ -232,14 +233,6 @@
margin-top: -1px;
}
-.nav-block {
- .controls {
- float: right;
- margin-top: 8px;
- padding-bottom: 8px;
- }
-}
-
.content-block {
padding: $gl-padding 0;
border-bottom: 1px solid $white-dark;
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 97a763671ba..767832e242c 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -249,7 +249,7 @@
padding: 6px 16px;
border-color: $border-color;
color: $gray-darkest;
- background-color: $gray-light;
+ background-color: $white-light;
&:hover,
&:active,
@@ -258,7 +258,6 @@
box-shadow: none;
border-color: lighten($blue-300, 20%);
color: $gray-darkest;
- background-color: $gray-light;
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index fc488b85138..db09118ba15 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -489,3 +489,50 @@ img.emoji {
.cursor-pointer {
cursor: pointer;
}
+
+// Make buttons/dropdowns full-width on mobile
+.full-width-mobile {
+ @include media-breakpoint-down(xs) {
+ width: 100%;
+
+ > .dropdown-menu,
+ > .btn {
+ width: 100%;
+ }
+ }
+}
+
+.onboarding-helper-container {
+ bottom: 40px;
+ right: 40px;
+ font-size: $gl-font-size-small;
+ background: $gray-100;
+ width: 200px;
+ border-radius: 24px;
+ box-shadow: 0 2px 4px $issue-boards-card-shadow;
+ z-index: 10000;
+
+ .collapsible {
+ max-height: 0;
+ transition: max-height 0.5s cubic-bezier(0, 1, 0, 1);
+ }
+
+ &.expanded {
+ border-bottom-right-radius: $border-radius-default;
+ border-bottom-left-radius: $border-radius-default;
+
+ .collapsible {
+ max-height: 1000px;
+ transition: max-height 1s ease-in-out;
+ }
+ }
+
+ .avatar {
+ border-color: darken($gray-normal, 10%);
+
+ img {
+ width: 32px;
+ height: 32px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index ef6f0633150..536a26a6ffe 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -453,6 +453,28 @@ span.idiff {
}
}
+ .note-container {
+ .user-avatar-link.new-comment {
+ position: absolute;
+ margin: 40px $gl-padding 0 116px;
+
+ ~ .note-edit-form form.edit-note {
+ @include media-breakpoint-up(sm) {
+ margin-left: $note-icon-gutter-width;
+ }
+ }
+ }
+ }
+
+ .diff-discussions:not(:last-child) .discussion .discussion-body {
+ padding-bottom: $gl-padding;
+
+ .discussion-reply-holder {
+ border-bottom: 1px solid $gray-100;
+ border-radius: 0;
+ }
+ }
+
.md-previewer {
padding: $gl-padding;
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 298610a0631..555a3fe0dc7 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -177,14 +177,6 @@ ul.content-list {
}
}
- .member-controls {
- float: none;
-
- @include media-breakpoint-up(sm) {
- float: right;
- }
- }
-
// When dragging a list item
&.ui-sortable-helper {
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index df40149f0a6..e7278554e6e 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -218,16 +218,22 @@
}
}
-@mixin build-trace-top-bar($height) {
+// Used in EE for Web Terminal
+@mixin build-trace-bar($height) {
height: $height;
min-height: $height;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
+ padding: $grid-size;
+}
+
+@mixin build-trace-top-bar($height) {
+ @include build-trace-bar($height);
+
position: -webkit-sticky;
position: sticky;
top: $header-height;
- padding: $grid-size;
.with-performance-bar & {
top: $header-height + $performance-bar-height;
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 31297b9d20c..ada8f2fe1a6 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -13,8 +13,8 @@
a,
button {
- padding: $gl-btn-padding;
- padding-bottom: 11px;
+ padding: $gl-padding-8;
+ padding-bottom: $gl-padding-8 + 1;
font-size: 14px;
line-height: 28px;
color: $gl-text-color-secondary;
@@ -58,8 +58,12 @@
}
.top-area {
- @include clearfix;
border-bottom: 1px solid $border-color;
+ display: flex;
+
+ @include media-breakpoint-down(md) {
+ flex-flow: column-reverse wrap;
+ }
.nav-text {
padding-top: 16px;
@@ -75,9 +79,8 @@
}
.nav-links {
- margin-bottom: 0;
border-bottom: 0;
- float: left;
+ flex: 1;
&.wide {
width: 100%;
@@ -98,16 +101,23 @@
&.mobile-separator {
border-bottom: 1px solid $border-color;
+ margin-bottom: $gl-padding-8;
}
}
}
.nav-controls {
display: inline-block;
- float: right;
text-align: right;
- padding: $gl-padding-8 0;
- margin-bottom: 0;
+
+ @include media-breakpoint-down(sm) {
+ margin-top: $gl-padding-8;
+ }
+
+ @include media-breakpoint-up(md) {
+ display: flex;
+ align-items: center;
+ }
> .btn,
> .btn-container,
@@ -115,8 +125,6 @@
> input,
> form {
margin-right: $gl-padding-top;
- display: inline-block;
- vertical-align: top;
&:last-child {
margin-right: 0;
@@ -143,7 +151,7 @@
@include media-breakpoint-up(lg) { width: 250px; }
}
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
padding-bottom: 0;
width: 100%;
@@ -153,7 +161,7 @@
.dropdown-toggle,
.dropdown-menu-toggle,
.form-control {
- margin: 0 0 10px;
+ margin: 0 0 $gl-padding-8;
display: block;
width: 100%;
}
@@ -165,7 +173,7 @@
form {
display: block;
height: auto;
- margin-bottom: 14px;
+ margin-bottom: $gl-padding-8;
input {
width: 100%;
@@ -236,20 +244,11 @@
width: 100%;
}
- @include media-breakpoint-down(xs) {
- flex-flow: row wrap;
-
+ @include media-breakpoint-down(md) {
.nav-controls {
$controls-margin: $btn-margin-5 - 2px;
flex: 0 0 100%;
-
- &.controls-flex {
- display: flex;
- flex-flow: row wrap;
- align-items: center;
- justify-content: center;
- padding: 0 0 $gl-padding-top;
- }
+ margin-top: $gl-padding-8;
.controls-item,
.controls-item-full,
@@ -326,8 +325,8 @@
.fade-right,
.fade-left {
- top: 16px;
- bottom: auto;
+ bottom: $gl-padding;
+ top: auto;
}
&.is-smaller {
@@ -367,6 +366,7 @@
display: flex;
border-bottom: 1px solid $border-color;
overflow: hidden;
+ align-items: center;
.nav-links {
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index e8176e59c19..42a739e88f7 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -42,8 +42,8 @@
}
}
- .avatar {
- margin-right: 15px;
+ img.avatar {
+ margin-right: $gl-padding;
}
.controls {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 0a3bd9bf4d1..dc451a97e17 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -641,6 +641,7 @@ $input-lg-width: 320px;
*/
$document-index-color: #888;
$help-shortcut-header-color: #333;
+$accepting-mr-label-color: #69d100;
/*
* Issues
diff --git a/app/assets/stylesheets/page_bundles/_ide_mixins.scss b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
index 896a3466cb4..9465dd5bed6 100644
--- a/app/assets/stylesheets/page_bundles/_ide_mixins.scss
+++ b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
@@ -2,17 +2,17 @@
display: flex;
flex-direction: column;
height: 100%;
- margin-top: -$grid-size;
- margin-bottom: -$grid-size;
- &.build-page .top-bar {
+ .top-bar {
+ @include build-trace-bar(35px);
+
top: 0;
- height: auto;
font-size: 12px;
border-top-right-radius: $border-radius-default;
- }
-
- .top-bar {
margin-left: -$gl-padding;
+
+ .controllers {
+ @include build-controllers(15px, center, false, 0, inline, 0);
+ }
}
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 0c1067bfacc..f08fa80495d 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -719,7 +719,7 @@ $ide-commit-header-height: 48px;
border: 1px solid $white-dark;
}
-.ide-commit-radios {
+.ide-commit-options {
label {
font-weight: normal;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 09ff518bbdf..5e3652db48f 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -31,14 +31,15 @@
width: 320px;
.dropdown-content {
- max-height: 162px;
+ max-height: 140px;
}
}
.issue-board-dropdown-content {
- margin: 0 8px 10px;
- padding-bottom: 10px;
- border-bottom: 1px solid $dropdown-divider-bg;
+ margin: 0;
+ padding: $gl-padding-4 $gl-padding $gl-padding;
+ border-bottom: 0;
+ color: $gl-text-color-secondary;
}
.issue-boards-page {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 5e5d298f8f2..3b0d740def3 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1008,6 +1008,10 @@ table.code {
display: block;
}
}
+
+ .note-edit-form {
+ margin-left: $note-icon-gutter-width;
+ }
}
.discussion-body .image .frame {
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 0a07747e0d4..656202f4e58 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -35,9 +35,6 @@
}
.group-nav-container .nav-controls {
- align-items: flex-start;
- padding: $gl-padding-top 0 0;
-
.group-filter-form {
flex: 1 1 auto;
margin-right: $gl-padding-8;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 4ba74d34664..79282f9043c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -69,7 +69,11 @@
}
.emoji-block {
- padding: 10px 0;
+ padding: $gl-padding-4 0;
+
+ @include media-breakpoint-down(md) {
+ padding: $gl-padding-8 0;
+ }
}
}
@@ -132,6 +136,10 @@
z-index: 200;
overflow: hidden;
+ @include media-breakpoint-down(sm) {
+ z-index: 251;
+ }
+
a:not(.btn) {
color: inherit;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index c7d2369a6b8..48289c8f381 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -258,8 +258,15 @@ ul.related-merge-requests > li {
}
}
-.discussion-reply-holder .note-edit-form {
- display: block;
+.discussion-reply-holder {
+ .avatar-note-form-holder .note-edit-form {
+ display: block;
+ margin-left: $note-icon-gutter-width;
+
+ @include media-breakpoint-down(xs) {
+ margin-left: 0;
+ }
+ }
}
.issue-sort-dropdown {
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 297f642681b..d8aabecc036 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -73,7 +73,8 @@
.login-body {
font-size: 13px;
- input + p {
+ input + p,
+ input ~ p.field-validation {
margin-top: 5px;
}
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index f8e273a2735..68af01f9ccc 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -20,17 +20,6 @@
}
}
- .list-item-name {
- @include media-breakpoint-up(sm) {
- float: left;
- width: 50%;
- }
-
- strong {
- font-weight: $gl-font-weight-bold;
- }
- }
-
.controls {
@include media-breakpoint-up(sm) {
display: flex;
@@ -43,10 +32,11 @@
.form-group {
margin-bottom: 0;
+ }
- @include media-breakpoint-down(sm) {
- display: block;
- margin-left: 5px;
+ .member-controls {
+ .fa {
+ line-height: inherit;
}
}
@@ -66,23 +56,12 @@
}
.member-form-control {
- @include media-breakpoint-down(sm) {
- width: $dropdown-member-form-control-width;
- margin-left: 0;
- padding-bottom: 5px;
- }
-
@include media-breakpoint-down(xs) {
margin-right: 0;
width: auto;
}
}
-.member-access-text {
- margin-left: auto;
- line-height: 43px;
-}
-
.member-search-form {
position: relative;
@@ -221,9 +200,6 @@
}
.content-list.members-list li {
- display: flex;
- justify-content: space-between;
-
.list-item-name {
float: none;
display: flex;
@@ -252,33 +228,24 @@
align-self: flex-start;
}
+ @include media-breakpoint-down(sm) {
+ .member-access-text {
+ margin: 0 0 $gl-padding-4 ($grid-size * 6);
+ }
+ }
+
@include media-breakpoint-down(xs) {
display: block;
- .controls > .btn {
- margin-left: 0;
- margin-right: 0;
+ .controls > .btn,
+ .controls .member-form-control {
+ margin: 0 0 $gl-padding-8;
display: block;
}
- .controls > .btn:last-child {
- margin-left: 5px;
- margin-right: 5px;
- width: auto;
- }
-
.form-control {
width: 100%;
}
-
- .member-access-text {
- line-height: 0;
- margin-left: 50px;
- }
-
- .member-controls {
- margin-top: 5px;
- }
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 77b40fe2d30..8cb3fab74e0 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -846,15 +846,40 @@
display: flex;
justify-content: space-between;
- @include media-breakpoint-down(sm) {
- flex-direction: column-reverse;
+ @include media-breakpoint-down(xs) {
+ .discussion-filter-container,
+ .line-resolve-all-container {
+ margin-bottom: $gl-padding-4;
+ }
}
.discussion-filter-container {
- margin-top: $gl-padding-8;
-
&:not(:only-child) {
- padding-right: $gl-padding-8;
+ margin: $gl-padding-4;
+ }
+ }
+
+ .merge-request-tabs {
+ height: $grid-size * 6;
+ }
+}
+
+// Wrap MR tabs/buttons so you don't have to scroll on desktop
+@include media-breakpoint-down(md) {
+ .merge-request-tabs-container,
+ .epic-tabs-container {
+ flex-direction: column-reverse;
+ padding-top: $gl-padding-8;
+ }
+}
+
+@include media-breakpoint-down(lg) {
+ .right-sidebar-expanded {
+ .merge-request-tabs-container,
+ .epic-tabs-container {
+ flex-direction: column-reverse;
+ align-items: flex-start;
+ padding-top: $gl-padding-8;
}
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 8c7b124dd33..c6bac33e888 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -59,6 +59,7 @@
border-radius: $border-radius-base;
transition: border-color ease-in-out 0.15s,
box-shadow ease-in-out 0.15s;
+ background-color: $white-light;
&.is-focused {
@extend .form-control:focus;
@@ -103,6 +104,11 @@
margin: auto;
align-items: center;
+ a {
+ color: $orange-600;
+ text-decoration: underline;
+ }
+
.icon {
margin-right: $issuable-warning-icon-margin;
vertical-align: text-bottom;
@@ -168,6 +174,16 @@
.discussion-form {
background-color: $white-light;
+
+ @include media-breakpoint-down(xs) {
+ .user-avatar-link {
+ display: none;
+ }
+
+ .note-edit-form {
+ margin-left: 0;
+ }
+ }
}
table {
@@ -234,13 +250,25 @@ table {
.diff-file,
.commit-diff {
.discussion-reply-holder {
- background-color: $white-light;
+ background-color: $gray-light;
border-radius: 0 0 3px 3px;
padding: $gl-padding;
+ border-top: 1px solid $gray-100;
+
+ + .new-note {
+ background-color: $gray-light;
+ border-top: 1px solid $gray-100;
+ }
&.is-replying {
padding-bottom: $gl-padding;
}
+
+ .user-avatar-link {
+ img {
+ margin-top: -3px;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 32477c20db6..69dd583bc5b 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -80,21 +80,17 @@ $note-form-margin-left: 72px;
}
}
- li.note {
- border-bottom: 1px solid $border-color;
- }
-
.replies-toggle {
background-color: $gray-light;
padding: $gl-padding-8 $gl-padding;
+ border-top: 1px solid $gray-100;
+ border-bottom: 1px solid $gray-100;
.collapse-replies-btn:hover {
color: $blue-600;
}
&.expanded {
- border-bottom: 1px solid $border-color;
-
span {
cursor: pointer;
}
@@ -211,8 +207,13 @@ $note-form-margin-left: 72px;
display: none;
}
+ .user-avatar-link img {
+ margin-top: $gl-padding-8;
+ }
+
.note-edit-form {
display: block;
+ margin-left: 0;
&.current-note-edit-form + .note-awards {
display: none;
@@ -264,8 +265,8 @@ $note-form-margin-left: 72px;
}
.system-note {
- padding: 6px 21px;
- margin: $gl-padding-24 0;
+ padding: $gl-padding-4 20px;
+ margin: $gl-padding 0;
background-color: transparent;
.note-header-info {
@@ -364,7 +365,7 @@ $note-form-margin-left: 72px;
height: $system-note-icon-size;
border: 1px solid $border-color;
border-radius: $system-note-icon-size;
- margin: -6px $gl-padding 0 0;
+ margin: -6px 20px 0 0;
svg {
width: $system-note-svg-size;
@@ -519,12 +520,30 @@ $note-form-margin-left: 72px;
}
}
-.commit-diff {
- .notes-content {
- background-color: $white-light;
+.code-commit .notes-content,
+.diff-viewer > .image ~ .note-container {
+ background-color: $white-light;
+
+ .avatar-note-form-holder {
+ .user-avatar-link img {
+ margin: 13px $gl-padding $gl-padding;
+ }
+
+ form,
+ ~ .discussion-form-container {
+ padding: $gl-padding;
+
+ @include media-breakpoint-up(sm) {
+ margin-left: $note-icon-gutter-width;
+ }
+ }
}
}
+.diff-viewer > .image ~ .note-container form.new-note {
+ margin-left: 0;
+}
+
.discussion-header,
.note-header-info {
a {
@@ -550,7 +569,7 @@ $note-form-margin-left: 72px;
}
.discussion-header {
- min-height: 72px;
+ min-height: 74px;
.note-header-info {
padding-bottom: 0;
@@ -563,8 +582,10 @@ $note-form-margin-left: 72px;
}
.unresolved {
- .note-header-info {
- margin-top: $gl-padding-8;
+ .discussion-header {
+ .note-header-info {
+ margin-top: $gl-padding-8;
+ }
}
}
@@ -762,15 +783,13 @@ $note-form-margin-left: 72px;
background-color: $white-light;
}
- a {
+ a:not(.learn-more) {
color: $blue-600;
}
}
.line-resolve-all-container {
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
- margin-right: 0;
- }
+ margin: $gl-padding-4;
> div {
white-space: nowrap;
@@ -786,6 +805,8 @@ $note-form-margin-left: 72px;
}
.btn {
+ line-height: $gl-line-height;
+
svg {
fill: $gray-darkest;
}
@@ -811,10 +832,11 @@ $note-form-margin-left: 72px;
.line-resolve-all {
vertical-align: middle;
display: inline-block;
- padding: 6px 10px;
+ padding: $gl-padding-4 10px;
background-color: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;
+ font-size: $gl-btn-small-font-size;
&.has-next-btn {
border-top-right-radius: 0;
@@ -830,6 +852,10 @@ $note-form-margin-left: 72px;
vertical-align: middle;
}
}
+
+ @include media-breakpoint-down(xs) {
+ flex: 1;
+ }
}
.line-resolve-btn {
diff --git a/app/controllers/acme_challenges_controller.rb b/app/controllers/acme_challenges_controller.rb
new file mode 100644
index 00000000000..67a39d8870b
--- /dev/null
+++ b/app/controllers/acme_challenges_controller.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AcmeChallengesController < ActionController::Base
+ def show
+ if acme_order
+ render plain: acme_order.challenge_file_content, content_type: 'text/plain'
+ else
+ head :not_found
+ end
+ end
+
+ private
+
+ def acme_order
+ @acme_order ||= PagesDomainAcmeOrder.find_by_domain_and_token(params[:domain], params[:token])
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 6e98d66d712..7321f719deb 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -440,6 +440,8 @@ class ApplicationController < ActionController::Base
end
def set_session_storage(&block)
+ return yield if sessionless_user?
+
Gitlab::Session.with_session(session, &block)
end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 73ebd4e0e42..80ee7c35906 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -12,9 +12,6 @@ class Clusters::ClustersController < Clusters::BaseController
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy]
before_action :update_applications_status, only: [:cluster_status]
- before_action only: [:show] do
- push_frontend_feature_flag(:metrics_time_window)
- end
helper_method :token_in_session
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index cb02581da37..98cd66cf6f9 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -4,6 +4,10 @@ class Projects::ClustersController < Clusters::ClustersController
prepend_before_action :project
before_action :repository
+ before_action do
+ push_frontend_feature_flag(:prometheus_computed_alerts)
+ end
+
layout 'project'
private
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index c342e1c80b0..e002a4d349b 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -11,10 +11,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
- push_frontend_feature_flag(:metrics_time_window)
push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint)
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
push_frontend_feature_flag(:grafana_dashboard_link)
+ push_frontend_feature_flag(:prometheus_computed_alerts)
end
def index
@@ -220,8 +220,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics_params
- return unless Feature.enabled?(:metrics_time_window, project)
-
params.require([:start, :end])
end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 7a80da53025..956093b972b 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -15,6 +15,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
alias_method :authenticated_user, :actor
# Git clients will not know what authenticity token to send along
+ skip_around_action :set_session_storage
skip_before_action :verify_authenticity_token
skip_before_action :repository
before_action :authenticate_user
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index eb469d2d714..f2a6268b3e9 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -7,11 +7,15 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
private
- # rubocop: disable CodeReuse/ActiveRecord
def merge_request
- @issuable = @merge_request ||= @project.merge_requests.includes(author: :status).find_by!(iid: params[:id])
+ @issuable =
+ @merge_request ||=
+ merge_request_includes(@project.merge_requests).find_by_iid!(params[:id])
+ end
+
+ def merge_request_includes(association)
+ association.includes(:metrics, :assignees, author: :status) # rubocop:disable CodeReuse/ActiveRecord
end
- # rubocop: enable CodeReuse/ActiveRecord
def merge_request_params
params.require(:merge_request).permit(merge_request_params_attributes)
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index c4dff95a4b9..1b8d479209b 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -50,7 +50,8 @@ module Projects
:runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_human_readable, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
- auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy]
+ auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy],
+ ci_cd_settings_attributes: [:default_git_depth]
)
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index a80ab3bcd28..cb25548c83f 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -25,6 +25,7 @@ class SearchController < ApplicationController
@show_snippets = search_service.show_snippets?
@search_results = search_service.search_results
@search_objects = search_service.search_objects
+ @display_options = search_service.display_options
render_commits if @scope == 'commits'
eager_load_user_status if @scope == 'users'
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index a374851e835..dd0d9105df6 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -29,15 +29,18 @@ module Types
# proc because we set complexity depending on arguments and number of
# items which can be loaded.
proc do |ctx, args, child_complexity|
- page_size = @max_page_size || ctx.schema.default_max_page_size
- limit_value = [args[:first], args[:last], page_size].compact.min
-
# Resolvers may add extra complexity depending on used arguments
complexity = child_complexity + self.resolver&.try(:resolver_complexity, args, child_complexity: child_complexity).to_i
- # Resolvers may add extra complexity depending on number of items being loaded.
- multiplier = self.resolver&.try(:complexity_multiplier, args).to_f
- complexity += complexity * limit_value * multiplier
+ field_defn = to_graphql
+
+ if field_defn.connection?
+ # Resolvers may add extra complexity depending on number of items being loaded.
+ page_size = field_defn.connection_max_page_size || ctx.schema.default_max_page_size
+ limit_value = [args[:first], args[:last], page_size].compact.min
+ multiplier = self.resolver&.try(:complexity_multiplier, args).to_f
+ complexity += complexity * limit_value * multiplier
+ end
complexity.to_i
end
diff --git a/app/helpers/ci_variables_helper.rb b/app/helpers/ci_variables_helper.rb
index e313015c937..fc51f00d052 100644
--- a/app/helpers/ci_variables_helper.rb
+++ b/app/helpers/ci_variables_helper.rb
@@ -27,4 +27,8 @@ module CiVariablesHelper
%w(File file)
]
end
+
+ def ci_variable_maskable_regex
+ Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/')
+ end
end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 2beb081ab77..36122d3a22a 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -57,12 +57,6 @@ module EmailsHelper
pluralize(valid_length, unit)
end
- def reset_token_expire_message
- link_tag = link_to('request a new one', new_user_password_url(user_email: @user.email))
- "This link is valid for #{password_reset_token_valid_time}. " \
- "After it expires, you can #{link_tag}."
- end
-
def header_logo
if current_appearance&.header_logo?
image_tag(
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 9deb783d289..b318b27992a 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -165,8 +165,46 @@ module VisibilityLevelHelper
!form_model.visibility_level_allowed?(level)
end
+ # Visibility level can be restricted in two ways:
+ #
+ # 1. The group permissions (e.g. a subgroup is private, which requires
+ # all projects to be private)
+ # 2. The global allowed visibility settings, set by the admin
+ def selected_visibility_level(form_model, requested_level)
+ requested_level =
+ if requested_level.present?
+ requested_level.to_i
+ else
+ default_project_visibility
+ end
+
+ [requested_level, max_allowed_visibility_level(form_model)].min
+ end
+
private
+ def max_allowed_visibility_level(form_model)
+ # First obtain the maximum visibility for the project or group
+ current_level = max_allowed_visibility_level_by_model(form_model)
+
+ # Now limit this by the global setting
+ Gitlab::VisibilityLevel.closest_allowed_level(current_level)
+ end
+
+ def max_allowed_visibility_level_by_model(form_model)
+ current_level = Gitlab::VisibilityLevel::PRIVATE
+
+ Gitlab::VisibilityLevel.values.sort.each do |value|
+ if disallowed_visibility_level?(form_model, value)
+ break
+ else
+ current_level = value
+ end
+ end
+
+ current_level
+ end
+
def visibility_level_errors_for_group(group, level_name)
group_name = link_to group.name, group_path(group)
change_visiblity = link_to 'change the visibility', edit_group_path(group)
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index aaa326afea5..89cc082d0bc 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -343,7 +343,7 @@ module Ci
end
def retryable?
- !archived? && (success? || failed?)
+ !archived? && (success? || failed? || canceled?)
end
def retries_count
diff --git a/app/models/concerns/maskable.rb b/app/models/concerns/maskable.rb
index 2943872ffab..e0f2c41b836 100644
--- a/app/models/concerns/maskable.rb
+++ b/app/models/concerns/maskable.rb
@@ -7,9 +7,9 @@ module Maskable
# * No escape characters
# * No variables
# * No spaces
- # * Minimal length of 8 characters
+ # * Minimal length of 8 characters from the Base64 alphabets (RFC4648)
# * Absolutely no fun is allowed
- REGEX = /\A\w{8,}\z/.freeze
+ REGEX = /\A[a-zA-Z0-9_+=\/-]{8,}\z/.freeze
included do
validates :masked, inclusion: { in: [true, false] }
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 2f0e078c807..b42adad94ba 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -75,4 +75,11 @@ module Taskable
def task_status_short
task_status(short: true)
end
+
+ def task_completion_status
+ @task_completion_status ||= {
+ count: tasks.summary.item_count,
+ completed_count: tasks.summary.complete_count
+ }
+ end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 8221b7de2b6..f75c32633b1 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -90,7 +90,7 @@ class DiffNote < Note
end
def banzai_render_context(field)
- super.merge(project: project, suggestions_filter_enabled: supports_suggestion?)
+ super.merge(suggestions_filter_enabled: true)
end
private
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 59416fb4b51..4fcaac75655 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -984,21 +984,6 @@ class MergeRequest < ApplicationRecord
end
end
- def reset_auto_merge
- return unless auto_merge_enabled?
-
- self.auto_merge_enabled = false
- self.merge_user = nil
- if merge_params
- merge_params.delete('should_remove_source_branch')
- merge_params.delete('commit_message')
- merge_params.delete('squash_commit_message')
- merge_params.delete('auto_merge_strategy')
- end
-
- self.save
- end
-
# Return array of possible target branches
# depends on target project of MR
def target_branches
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index 6f057f79ef6..9b2bbb7eba5 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -50,7 +50,7 @@ class NotificationRecipient
when :mention
@type == :mention
when :participating
- !excluded_participating_action? && %i[participating mention watch].include?(@type)
+ @custom_action == :failed_pipeline || %i[participating mention].include?(@type)
when :custom
custom_enabled? || %i[participating mention].include?(@type)
when :watch
@@ -106,12 +106,6 @@ class NotificationRecipient
NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
end
- def excluded_participating_action?
- return false unless @custom_action
-
- NotificationSetting::EXCLUDED_PARTICIPATING_EVENTS.include?(@custom_action)
- end
-
private
def read_ability
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 61af5c09ae4..8306b11a7b6 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -54,14 +54,11 @@ class NotificationSetting < ApplicationRecord
self.class.email_events(source)
end
- EXCLUDED_PARTICIPATING_EVENTS = [
- :success_pipeline
- ].freeze
-
EXCLUDED_WATCHER_EVENTS = [
:push_to_merge_request,
- :issue_due
- ].push(*EXCLUDED_PARTICIPATING_EVENTS).freeze
+ :issue_due,
+ :success_pipeline
+ ].freeze
def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source)
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 407d85b1520..524df30289e 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -5,6 +5,7 @@ class PagesDomain < ApplicationRecord
VERIFICATION_THRESHOLD = 3.days.freeze
belongs_to :project
+ has_many :acme_orders, class_name: "PagesDomainAcmeOrder"
validates :domain, hostname: { allow_numeric_hostname: true }
validates :domain, uniqueness: { case_sensitive: false }
@@ -134,6 +135,14 @@ class PagesDomain < ApplicationRecord
"#{VERIFICATION_KEY}=#{verification_code}"
end
+ def certificate=(certificate)
+ super(certificate)
+
+ # set nil, if certificate is nil
+ self.certificate_valid_not_before = x509&.not_before
+ self.certificate_valid_not_after = x509&.not_after
+ end
+
private
def set_verification_code
@@ -186,7 +195,7 @@ class PagesDomain < ApplicationRecord
end
def x509
- return unless certificate
+ return unless certificate.present?
@x509 ||= OpenSSL::X509::Certificate.new(certificate)
rescue OpenSSL::X509::CertificateError
diff --git a/app/models/pages_domain_acme_order.rb b/app/models/pages_domain_acme_order.rb
new file mode 100644
index 00000000000..63d7fbc8206
--- /dev/null
+++ b/app/models/pages_domain_acme_order.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class PagesDomainAcmeOrder < ApplicationRecord
+ belongs_to :pages_domain
+
+ scope :expired, -> { where("expires_at < ?", Time.now) }
+
+ validates :pages_domain, presence: true
+ validates :expires_at, presence: true
+ validates :url, presence: true
+ validates :challenge_token, presence: true
+ validates :challenge_file_content, presence: true
+ validates :private_key, presence: true
+
+ attr_encrypted :private_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
+ def self.find_by_domain_and_token(domain_name, challenge_token)
+ joins(:pages_domain).find_by(pages_domains: { domain: domain_name }, challenge_token: challenge_token)
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 78d54571d94..e64a4b313aa 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -292,6 +292,7 @@ class Project < ApplicationRecord
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops, update_only: true
+ accepts_nested_attributes_for :ci_cd_settings, update_only: true
accepts_nested_attributes_for :remote_mirrors,
allow_destroy: true,
@@ -310,6 +311,7 @@ class Project < ApplicationRecord
delegate :root_ancestor, to: :namespace, allow_nil: true
delegate :last_pipeline, to: :commit, allow_nil: true
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
+ delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings
# Validations
validates :creator, presence: true, on: :create
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index 1414164b703..492d50766ea 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -6,6 +6,18 @@ class ProjectCiCdSetting < ApplicationRecord
# The version of the schema that first introduced this model/table.
MINIMUM_SCHEMA_VERSION = 20180403035759
+ DEFAULT_GIT_DEPTH = 50
+
+ before_create :set_default_git_depth
+
+ validates :default_git_depth,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: 0,
+ less_than_or_equal_to: 1000
+ },
+ allow_nil: true
+
def self.available?
@available ||=
ActiveRecord::Migrator.current_version >= MINIMUM_SCHEMA_VERSION
@@ -15,4 +27,10 @@ class ProjectCiCdSetting < ApplicationRecord
@available = nil
super
end
+
+ private
+
+ def set_default_git_depth
+ self.default_git_depth ||= DEFAULT_GIT_DEPTH
+ end
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 0542581c6e0..6bcb051bff6 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -72,6 +72,8 @@ class ProjectFeature < ApplicationRecord
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
+ scope :for_project_id, -> (project) { where(project: project) }
+
def feature_available?(feature, user)
# This feature might not be behind a feature flag at all, so default to true
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb
index 957be685aea..175c2ebf197 100644
--- a/app/models/project_services/youtrack_service.rb
+++ b/app/models/project_services/youtrack_service.rb
@@ -5,12 +5,12 @@ class YoutrackService < IssueTrackerService
prop_accessor :description, :project_url, :issues_url
- # {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1
+ # {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1, gl-030
def self.reference_pattern(only_long: false)
if only_long
- /(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)/
+ /(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+)/
else
- /(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)|(#{Issue.reference_prefix}(?<issue>\d+))/
+ /(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+)|(#{Issue.reference_prefix}(?<issue>\d+))/
end
end
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 6d46e0bf18a..471b6d3b726 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -25,14 +25,16 @@ module Ci
end
def git_depth
- strong_memoize(:git_depth) do
- git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value)
- git_depth.to_i
- end
+ if git_depth_variable
+ git_depth_variable[:value]
+ else
+ project.default_git_depth
+ end.to_i
end
def refspecs
specs = []
+ specs << refspec_for_merge_request_ref if merge_request_ref?
if git_depth > 0
specs << refspec_for_branch(ref) if branch? || legacy_detached_merge_request_pipeline?
@@ -42,8 +44,6 @@ module Ci
specs << refspec_for_tag
end
- specs << refspec_for_merge_request_ref if merge_request_ref?
-
specs
end
@@ -89,5 +89,11 @@ module Ci
def refspec_for_merge_request_ref
"+#{ref}:#{ref}"
end
+
+ def git_depth_variable
+ strong_memoize(:git_depth_variable) do
+ variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }
+ end
+ end
end
end
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 914ad628a99..36e601f45c5 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -44,4 +44,12 @@ class IssueEntity < IssuableEntity
expose :preview_note_path do |issue|
preview_markdown_path(issue.project, target_type: 'Issue', target_id: issue.iid)
end
+
+ expose :confidential_issues_docs_path, if: -> (issue) { issue.confidential? } do |issue|
+ help_page_path('user/project/issues/confidential_issues.md')
+ end
+
+ expose :locked_discussion_docs_path, if: -> (issue) { issue.discussion_locked? } do |issue|
+ help_page_path('user/discussions/index.md', anchor: 'lock-discussions')
+ end
end
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
new file mode 100644
index 00000000000..058105db3a4
--- /dev/null
+++ b/app/services/auto_merge/base_service.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module AutoMerge
+ class BaseService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ def execute(merge_request)
+ merge_request.merge_params.merge!(params)
+ merge_request.auto_merge_enabled = true
+ merge_request.merge_user = current_user
+ merge_request.auto_merge_strategy = strategy
+
+ return :failed unless merge_request.save
+
+ yield if block_given?
+
+ strategy.to_sym
+ end
+
+ def cancel(merge_request)
+ if cancel_auto_merge(merge_request)
+ yield if block_given?
+
+ success
+ else
+ error("Can't cancel the automatic merge", 406)
+ end
+ end
+
+ private
+
+ def strategy
+ strong_memoize(:strategy) do
+ self.class.name.demodulize.remove('Service').underscore
+ end
+ end
+
+ def cancel_auto_merge(merge_request)
+ merge_request.auto_merge_enabled = false
+ merge_request.merge_user = nil
+
+ merge_request.merge_params&.except!(
+ 'should_remove_source_branch',
+ 'commit_message',
+ 'squash_commit_message',
+ 'auto_merge_strategy'
+ )
+
+ merge_request.save
+ end
+ end
+end
diff --git a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
index d0586468859..c41073a73e9 100644
--- a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
+++ b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
@@ -1,32 +1,12 @@
# frozen_string_literal: true
module AutoMerge
- class MergeWhenPipelineSucceedsService < BaseService
+ class MergeWhenPipelineSucceedsService < AutoMerge::BaseService
def execute(merge_request)
- return :failed unless merge_request.actual_head_pipeline
-
- if merge_request.actual_head_pipeline.active?
- merge_request.merge_params.merge!(params)
-
- unless merge_request.auto_merge_enabled?
- merge_request.auto_merge_enabled = true
- merge_request.merge_user = @current_user
- merge_request.auto_merge_strategy = AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
-
- SystemNoteService.merge_when_pipeline_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit)
+ super do
+ if merge_request.saved_change_to_auto_merge_enabled?
+ SystemNoteService.merge_when_pipeline_succeeds(merge_request, project, current_user, merge_request.diff_head_commit)
end
-
- return :failed unless merge_request.save
-
- :merge_when_pipeline_succeeds
- elsif merge_request.actual_head_pipeline.success?
- # This can be triggered when a user clicks the auto merge button while
- # the tests finish at about the same time
- merge_request.merge_async(current_user.id, merge_params)
-
- :success
- else
- :failed
end
end
@@ -38,12 +18,8 @@ module AutoMerge
end
def cancel(merge_request)
- if merge_request.reset_auto_merge
+ super do
SystemNoteService.cancel_merge_when_pipeline_succeeds(merge_request, @project, @current_user)
-
- success
- else
- error("Can't cancel the automatic merge", 406)
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index bb9062e9b40..2cfed62ce49 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -60,31 +60,7 @@ module MergeRequests
end
def create_pipeline_for(merge_request, user)
- return unless can_create_pipeline_for?(merge_request)
-
- create_detached_merge_request_pipeline(merge_request, user)
- end
-
- def create_detached_merge_request_pipeline(merge_request, user)
- if can_use_merge_request_ref?(merge_request)
- Ci::CreatePipelineService.new(merge_request.source_project, user,
- ref: merge_request.ref_path)
- .execute(:merge_request_event, merge_request: merge_request)
- else
- Ci::CreatePipelineService.new(merge_request.source_project, user,
- ref: merge_request.source_branch)
- .execute(:merge_request_event, merge_request: merge_request)
- end
- end
-
- def can_create_pipeline_for?(merge_request)
- ##
- # UpdateMergeRequestsWorker could be retried by an exception.
- # pipelines for merge request should not be recreated in such case.
- return false if merge_request.find_actual_head_pipeline&.triggered_by_merge_request?
- return false if merge_request.has_no_commits?
-
- true
+ MergeRequests::CreatePipelineService.new(project, user).execute(merge_request)
end
def can_use_merge_request_ref?(merge_request)
diff --git a/app/services/merge_requests/create_pipeline_service.rb b/app/services/merge_requests/create_pipeline_service.rb
new file mode 100644
index 00000000000..03246cc1920
--- /dev/null
+++ b/app/services/merge_requests/create_pipeline_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class CreatePipelineService < MergeRequests::BaseService
+ def execute(merge_request)
+ return unless can_create_pipeline_for?(merge_request)
+
+ create_detached_merge_request_pipeline(merge_request)
+ end
+
+ def create_detached_merge_request_pipeline(merge_request)
+ if can_use_merge_request_ref?(merge_request)
+ Ci::CreatePipelineService.new(merge_request.source_project, current_user,
+ ref: merge_request.ref_path)
+ .execute(:merge_request_event, merge_request: merge_request)
+ else
+ Ci::CreatePipelineService.new(merge_request.source_project, current_user,
+ ref: merge_request.source_branch)
+ .execute(:merge_request_event, merge_request: merge_request)
+ end
+ end
+
+ def can_create_pipeline_for?(merge_request)
+ ##
+ # UpdateMergeRequestsWorker could be retried by an exception.
+ # pipelines for merge request should not be recreated in such case.
+ return false if !allow_duplicate && merge_request.find_actual_head_pipeline&.triggered_by_merge_request?
+ return false if merge_request.has_no_commits?
+
+ true
+ end
+
+ def allow_duplicate
+ params[:allow_duplicate]
+ end
+ end
+end
diff --git a/app/services/pages_domains/create_acme_order_service.rb b/app/services/pages_domains/create_acme_order_service.rb
new file mode 100644
index 00000000000..c600f497fa5
--- /dev/null
+++ b/app/services/pages_domains/create_acme_order_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class CreateAcmeOrderService
+ attr_reader :pages_domain
+
+ def initialize(pages_domain)
+ @pages_domain = pages_domain
+ end
+
+ def execute
+ lets_encrypt_client = Gitlab::LetsEncrypt::Client.new
+ order = lets_encrypt_client.new_order(pages_domain.domain)
+
+ challenge = order.new_challenge
+
+ private_key = OpenSSL::PKey::RSA.new(4096)
+ saved_order = pages_domain.acme_orders.create!(
+ url: order.url,
+ expires_at: order.expires,
+ private_key: private_key.to_pem,
+
+ challenge_token: challenge.token,
+ challenge_file_content: challenge.file_content
+ )
+
+ challenge.request_validation
+ saved_order
+ end
+ end
+end
diff --git a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
new file mode 100644
index 00000000000..2dfe1a3d8ca
--- /dev/null
+++ b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class ObtainLetsEncryptCertificateService
+ attr_reader :pages_domain
+
+ def initialize(pages_domain)
+ @pages_domain = pages_domain
+ end
+
+ def execute
+ pages_domain.acme_orders.expired.delete_all
+ acme_order = pages_domain.acme_orders.first
+
+ unless acme_order
+ ::PagesDomains::CreateAcmeOrderService.new(pages_domain).execute
+ return
+ end
+
+ api_order = ::Gitlab::LetsEncrypt::Client.new.load_order(acme_order.url)
+
+ # https://tools.ietf.org/html/rfc8555#section-7.1.6 - statuses diagram
+ case api_order.status
+ when 'ready'
+ api_order.request_certificate(private_key: acme_order.private_key, domain: pages_domain.domain)
+ when 'valid'
+ save_certificate(acme_order.private_key, api_order)
+ acme_order.destroy!
+ # when 'invalid'
+ # TODO: implement error handling
+ end
+ end
+
+ private
+
+ def save_certificate(private_key, api_order)
+ certificate = api_order.certificate
+ pages_domain.update!(key: private_key, certificate: certificate)
+ end
+ end
+end
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index 7386530f45f..2b4c4ae68e2 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -38,7 +38,9 @@ class PreviewMarkdownService < BaseService
head_sha: params[:head_sha],
start_sha: params[:start_sha])
- Gitlab::Diff::SuggestionsParser.parse(text, position: position, project: project)
+ Gitlab::Diff::SuggestionsParser.parse(text, position: position,
+ project: project,
+ supports_suggestion: params[:preview_suggestions])
end
def preview_sugestions?
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index fc234bafc57..0b4ab7b8e4d 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -36,18 +36,22 @@ module Projects
def fork_new_project
new_params = {
- visibility_level: allowed_visibility_level,
- description: @project.description,
- name: target_name,
- path: target_path,
- shared_runners_enabled: @project.shared_runners_enabled,
- namespace_id: target_namespace.id,
- fork_network: fork_network,
+ visibility_level: allowed_visibility_level,
+ description: @project.description,
+ name: target_name,
+ path: target_path,
+ shared_runners_enabled: @project.shared_runners_enabled,
+ namespace_id: target_namespace.id,
+ fork_network: fork_network,
+ # We need to set default_git_depth to 0 for the forked project when
+ # @project.default_git_depth is nil in order to keep the same behaviour
+ # and not get ProjectCiCdSetting::DEFAULT_GIT_DEPTH set on create
+ ci_cd_settings_attributes: { default_git_depth: @project.default_git_depth || 0 },
# We need to assign the fork network membership after the project has
# been instantiated to avoid ActiveRecord trying to create it when
# initializing the project, as that would cause a foreign key constraint
# exception.
- relations_block: -> (project) { build_fork_network_member(project) }
+ relations_block: -> (project) { build_fork_network_member(project) }
}
if @project.avatar.present? && @project.avatar.image?
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index e0cbfac2420..302510341ac 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -52,6 +52,10 @@ class SearchService
@search_objects ||= search_results.objects(scope, params[:page])
end
+ def display_options
+ @display_options ||= search_results.display_options(scope)
+ end
+
private
def search_service
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index a161fbd064e..c6781e91cfd 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -1,10 +1,10 @@
-- page_title _("Report abuse to GitLab")
+- page_title _("Report abuse to admin")
%h3.page-title
- = _("Report abuse to GitLab")
+ = _("Report abuse to admin")
%p
- = _("Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately.")
+ = _("Please use this form to report to the admin users who create spam issues, comments or behave inappropriately.")
%p
- = _("A member of GitLab's abuse team will review your report as soon as possible.")
+ = _("A member of the abuse team will review your report as soon as possible.")
%hr
= form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f|
= form_errors(@abuse_report)
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index 464b9faf282..94102b4dcd0 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -6,7 +6,7 @@
= s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.row
- .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint } }
+ .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint, maskable_regex: ci_variable_maskable_regex } }
.hide.alert.alert-danger.js-ci-variable-error-box
%ul.ci-variable-list
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 214630d245a..8212fb8bb33 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -34,7 +34,7 @@
= icon('spinner spin')
.todos-filters
- .row-content-block.second-block
+ .issues-details-filters.row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form d-sm-flex' do
.filter-categories.flex-fill
.filter-item.inline
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 383fd5130ce..5eba819172b 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -1,3 +1,5 @@
+- max_name_length = 128
+- max_username_length = 255
#register-pane.tab-pane.login-box{ role: 'tabpanel' }
.login-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
@@ -5,13 +7,13 @@
= render "devise/shared/error_messages", resource: resource
.name.form-group
= f.label :name, _('Full name'), class: 'label-bold'
- = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji", required: true, title: _("This field is required.")
+ = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length } }, required: true, title: _("This field is required.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
- %p.validation-error.hide= _('Username is already taken.')
- %p.validation-success.hide= _('Username is available.')
- %p.validation-pending.hide= _('Checking username availability...')
+ = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
+ %p.validation-error.field-validation.hide= _('Username is already taken.')
+ %p.validation-success.field-validation.hide= _('Username is available.')
+ %p.validation-pending.field-validation.hide= _('Checking username availability...')
.form-group
= f.label :email, class: 'label-bold'
= f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.")
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
index 30b00ca86b3..0a5541c3e82 100644
--- a/app/views/discussions/_notes.html.haml
+++ b/app/views/discussions/_notes.html.haml
@@ -19,20 +19,24 @@
.discussion-reply-holder
- if can_create_note?
+ %a.user-avatar-link.d-none.d-sm-block{ href: user_path(current_user) }
+ = image_tag avatar_icon_for_user(current_user), alt: current_user.to_reference, class: 'avatar s40'
- if discussion.potentially_resolvable?
- line_type = local_assigns.fetch(:line_type, nil)
- .btn-group.discussion-with-resolve-btn{ role: "group" }
- .btn-group{ role: "group" }
- = link_to_reply_discussion(discussion, line_type)
+ .discussion-with-resolve-btn
+ .btn-group.discussion-with-resolve-btn{ role: "group" }
+ .btn-group{ role: "group" }
+ = link_to_reply_discussion(discussion, line_type)
- = render "discussions/resolve_all", discussion: discussion
+ = render "discussions/resolve_all", discussion: discussion
- .btn-group.discussion-actions
- = render "discussions/new_issue_for_discussion", discussion: discussion, merge_request: discussion.noteable
- = render "discussions/jump_to_next", discussion: discussion
+ .btn-group.discussion-actions
+ = render "discussions/new_issue_for_discussion", discussion: discussion, merge_request: discussion.noteable
+ = render "discussions/jump_to_next", discussion: discussion
- else
- = link_to_reply_discussion(discussion)
+ .discussion-with-resolve-btn
+ = link_to_reply_discussion(discussion)
- elsif !current_user
.disabled-comment.text-center
Please
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 77fe88dacb7..255a9ad038c 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -9,7 +9,7 @@
= render 'groups/home_panel'
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
- .top-area.group-nav-container
+ .top-area.group-nav-container.justify-content-between
.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 47710b9e9e5..54028dc8554 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -19,17 +19,17 @@
- if dashboard_nav_link?(:activity)
= nav_link(path: 'dashboard#activity', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: _('Activity') do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity' do
= _('Activity')
- if dashboard_nav_link?(:milestones)
= nav_link(controller: 'dashboard/milestones', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: _('Milestones') do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones' do
= _('Milestones')
- if dashboard_nav_link?(:snippets)
= nav_link(controller: 'dashboard/snippets', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets qa-snippets-link', title: _('Snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets qa-snippets-link' do
= _('Snippets')
- if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets])
@@ -41,47 +41,47 @@
%ul
- if dashboard_nav_link?(:activity)
= nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, title: _('Activity') do
+ = link_to activity_dashboard_path do
= _('Activity')
- if dashboard_nav_link?(:milestones)
= nav_link(controller: 'dashboard/milestones') do
- = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: _('Milestones') do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones' do
= _('Milestones')
- if dashboard_nav_link?(:snippets)
= nav_link(controller: 'dashboard/snippets') do
- = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets' do
= _('Snippets')
-
- = render_if_exists 'dashboard/operations/nav_link'
+ %li.dropdown.d-lg-none
+ = render_if_exists 'dashboard/operations/nav_link_list'
- if can?(current_user, :read_instance_statistics)
- = nav_link(controller: [:conversational_development_index, :cohorts]) do
- = link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = nav_link(controller: [:conversational_development_index, :cohorts], html_options: { class: 'd-lg-none' }) do
+ = link_to instance_statistics_root_path do
= _('Instance Statistics')
- if current_user.admin?
= nav_link(controller: 'admin/dashboard') do
- = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to admin_root_path, class: 'd-lg-none admin-icon qa-admin-area-link' do
= _('Admin Area')
- if Gitlab::Sherlock.enabled?
%li
- = link_to sherlock_transactions_path, class: 'admin-icon', title: _('Sherlock Transactions'),
- data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to sherlock_transactions_path, class: 'd-lg-none admin-icon' do
= _('Sherlock Transactions')
-# Shortcut to Dashboard > Projects
- if dashboard_nav_link?(:projects)
%li.hidden
- = link_to dashboard_projects_path, title: _('Projects'), class: 'dashboard-shortcuts-projects' do
+ = link_to dashboard_projects_path, class: 'dashboard-shortcuts-projects' do
= _('Projects')
- if current_controller?('ide')
%li.line-separator.d-none.d-sm-block
= nav_link(controller: 'ide') do
- = link_to '#', class: 'dashboard-shortcuts-web-ide', title: _('Web IDE') do
+ = link_to '#', class: 'dashboard-shortcuts-web-ide' do
= _('Web IDE')
- = render_if_exists 'dashboard/operations/nav_link'
+ %li.dropdown{ class: 'd-none d-lg-block' }
+ = render_if_exists 'dashboard/operations/nav_link'
- if can?(current_user, :read_instance_statistics)
= nav_link(controller: [:conversational_development_index, :cohorts], html_options: { class: "d-none d-lg-block d-xl-block"}) do
= link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
index dfbb5c75bd3..ec135ae994f 100644
--- a/app/views/notify/new_user_email.html.haml
+++ b/app/views/notify/new_user_email.html.haml
@@ -13,4 +13,5 @@
%p
= link_to "Click here to set your password", edit_password_url(@user, reset_password_token: @token)
%p
- = raw reset_token_expire_message
+ This link is valid for #{password_reset_token_valid_time}.
+ After it expires, you can #{link_to("request a new one", new_user_password_url(user_email: @user.email))}.
diff --git a/app/views/notify/new_user_email.text.erb b/app/views/notify/new_user_email.text.erb
index f3f20f3bfba..7e0db75472d 100644
--- a/app/views/notify/new_user_email.text.erb
+++ b/app/views/notify/new_user_email.text.erb
@@ -1,10 +1,17 @@
Hi <%= sanitize_name(@user.name) %>!
+<% if Gitlab::CurrentSettings.allow_signup? %>
+Your account has been created successfully.
+<% else %>
The Administrator created an account for you. Now you are a member of the company GitLab application.
+<% end %>
login.................. <%= @user.email %>
+
<% if @user.created_by_id %>
- <%= link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token) %>
+Click here to set your password:
+<%= edit_password_url(@user, :reset_password_token => @token) %>
- <%= reset_token_expire_message %>
+This link is valid for <%= password_reset_token_valid_time %>. After it expires, you can request a new one here:
+<%= new_user_password_url(user_email: @user.email) %>
<% end %>
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 46384bc28ef..4ebfaff0860 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -5,7 +5,8 @@
.col-lg-4.application-theme
%h4.prepend-top-0
= s_('Preferences|Navigation theme')
- %p= _('Customize the appearance of the application header and navigation sidebar.')
+ %p
+ = s_('Preferences|Customize the appearance of the application header and navigation sidebar.')
.col-lg-8.application-theme
- Gitlab::Themes.each do |theme|
= label_tag do
@@ -18,9 +19,9 @@
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- = _('Syntax highlighting theme')
+ = s_('Preferences|Syntax highlighting theme')
%p
- = _('This setting allows you to customize the appearance of the syntax.')
+ = s_('Preferences|This setting allows you to customize the appearance of the syntax.')
= succeed '.' do
= link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
.col-lg-8.syntax-theme
@@ -35,31 +36,31 @@
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- = _('Behavior')
+ = s_('Preferences|Behavior')
%p
- = _('This setting allows you to customize the behavior of the system layout and default views.')
+ = s_('Preferences|This setting allows you to customize the behavior of the system layout and default views.')
= succeed '.' do
= link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
.col-lg-8
.form-group
= f.label :layout, class: 'label-bold' do
- = _('Layout width')
+ = s_('Preferences|Layout width')
= f.select :layout, layout_choices, {}, class: 'form-control'
.form-text.text-muted
- = _('Choose between fixed (max. 1280px) and fluid (100%%) application layout.')
+ = s_('Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout.')
.form-group
= f.label :dashboard, class: 'label-bold' do
- = _('Default dashboard')
+ = s_('Preferences|Default dashboard')
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
= render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
.form-group
= f.label :project_view, class: 'label-bold' do
- = _('Project overview content')
+ = s_('Preferences|Project overview content')
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.form-text.text-muted
- = _('Choose what content you want to see on a project’s overview page.')
+ = s_('Preferences|Choose what content you want to see on a project’s overview page.')
.col-sm-12
%hr
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 1ab467a3710..c21d333f21a 100644
--- a/app/views/projects/_merge_request_merge_checks_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_checks_settings.html.haml
@@ -3,7 +3,7 @@
.form-group
%b= s_('ProjectSettings|Merge checks')
%p.text-secondary= s_('ProjectSettings|These checks must pass before merge requests can be merged')
- .form-check.mb-2.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) }
+ .form-check.mb-2.builds-feature
= 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')
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 9ae84a909a5..e423631ec99 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -1,4 +1,4 @@
-- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
+- visibility_level = selected_visibility_level(@project, params.dig(:project, :visibility_level))
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
- hide_init_with_readme = local_assigns.fetch(:hide_init_with_readme, false)
- track_label = local_assigns.fetch(:track_label, 'blank_project')
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 311b0be19ab..9587ea4696b 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,7 +1,7 @@
/ Side-by-side diff view
.text-file{ data: diff_view_data }
- %table.diff-wrap-lines.code.js-syntax-highlight
+ %table.diff-wrap-lines.code.code-commit.js-syntax-highlight
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 018c5b38536..641a0689c26 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -3,7 +3,7 @@
.suppressed-container
%a.show-suppressed-diff.cursor-pointer.js-show-suppressed-diff= _("Changes suppressed. Click to show.")
-%table.text-file.diff-wrap-lines.code.js-syntax-highlight.commit-diff{ data: diff_view_data, class: too_big ? 'hide' : '' }
+%table.text-file.diff-wrap-lines.code.code-commit.js-syntax-highlight.commit-diff{ data: diff_view_data, class: too_big ? 'hide' : '' }
= render partial: "projects/diffs/line",
collection: diff_file.highlighted_diff_lines,
as: :line,
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index fbd70cd1906..457b2936278 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -8,18 +8,18 @@
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
- refs_path = refs_namespace_project_path(@project.namespace, @project, search: '')
- .create-mr-dropdown-wrap.d-inline-block{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
- .btn-group.unavailable
+ .create-mr-dropdown-wrap.d-inline-block.full-width-mobile{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
+ .btn-group.btn-group-sm.unavailable
%button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
= icon('spinner', class: 'fa-spin')
%span.text
Checking branch availability…
- .btn-group.available.hidden
+ .btn-group.btn-group-sm.available.hidden
%button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } }
= value
- %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' }, display: 'static' } }
+ %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle.flex-grow-0{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' }, display: 'static' } }
= icon('caret-down')
.droplab-dropdown
diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
index 03226de120d..7bd5c437942 100644
--- a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
+++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
@@ -1,5 +1,5 @@
%inline-conflict-lines{ "inline-template" => "true", ":file" => "file" }
- %table.diff-wrap-lines.code.js-syntax-highlight
+ %table.diff-wrap-lines.code.code-commit.js-syntax-highlight
%tr.line_holder.diff-inline{ "v-for" => "line in file.inlineLines" }
%td.diff-line-num.new_line{ ":class" => "lineCssClass(line)", "v-if" => "!line.isHeader" }
%a {{line.new_line}}
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 05aeb5d972b..a201fafb949 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -31,29 +31,26 @@
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
.merge-request-tabs-container
- .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- %ul.merge-request-tabs.nav-tabs.nav.nav-links.scrolling-tabs
- %li.notes-tab.qa-notes-tab
- = tab_link_for @merge_request, :show, force_link: @commit.present? do
- = _("Discussion")
- %span.badge.badge-pill= @merge_request.related_notes.user.count
- - if @merge_request.source_project
- %li.commits-tab
- = tab_link_for @merge_request, :commits do
- = _("Commits")
- %span.badge.badge-pill= @commits_count
- - if @pipelines.any?
- %li.pipelines-tab
- = tab_link_for @merge_request, :pipelines do
- = _("Pipelines")
- %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
- %li.diffs-tab.qa-diffs-tab
- = tab_link_for @merge_request, :diffs do
- = _("Changes")
- %span.badge.badge-pill= @merge_request.diff_size
- .d-inline-flex.flex-wrap
+ %ul.merge-request-tabs.nav-tabs.nav.nav-links
+ %li.notes-tab.qa-notes-tab
+ = tab_link_for @merge_request, :show, force_link: @commit.present? do
+ = _("Discussion")
+ %span.badge.badge-pill= @merge_request.related_notes.user.count
+ - if @merge_request.source_project
+ %li.commits-tab
+ = tab_link_for @merge_request, :commits do
+ = _("Commits")
+ %span.badge.badge-pill= @commits_count
+ - if @pipelines.any?
+ %li.pipelines-tab
+ = tab_link_for @merge_request, :pipelines do
+ = _("Pipelines")
+ %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
+ %li.diffs-tab.qa-diffs-tab
+ = tab_link_for @merge_request, :diffs do
+ = _("Changes")
+ %span.badge.badge-pill= @merge_request.diff_size
+ .d-flex.flex-wrap.align-items-center.justify-content-lg-end
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@merge_request),
notes_filters: UserPreference.notes_filters.to_json } }
#js-vue-discussion-counter
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 8de84f82e9f..8a6e5fde99b 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -11,7 +11,7 @@
- unless is_current_user
%li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
- = _('Report abuse to GitLab')
+ = _('Report abuse to admin')
- if note_editable
%li
= link_to note_url(note), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?' }, remote: true, class: 'js-note-delete' do
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index b38b8e3f686..2d108a1cba5 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -26,6 +26,14 @@
%hr
.form-group
+ = f.fields_for :ci_cd_settings_attributes, @project.ci_cd_settings do |form|
+ = form.label :default_git_depth, _('Git shallow clone'), class: 'label-bold'
+ = form.number_field :default_git_depth, { class: 'form-control', min: 0, max: 1000 }
+ %p.form-text.text-muted
+ = _('The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time.')
+
+ %hr
+ .form-group
= f.label :build_timeout_human_readable, _('Timeout'), class: 'label-bold'
= f.text_field :build_timeout_human_readable, class: 'form-control'
%p.form-text.text-muted
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 458096f9dd6..2e78b0bff3e 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -9,7 +9,7 @@
.nav-text.row-main-content
= s_('TagsPage|Tags give the ability to mark specific points in history as being important')
- .nav-controls.row-fixed-content
+ .nav-controls
= form_tag(filter_tags_path, method: :get) do
= search_field_tag :search, params[:search], { placeholder: s_('TagsPage|Filter by tag name'), id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false }
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 0be62bc5612..59232372150 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -18,7 +18,7 @@
- else
= s_("TagsPage|Can't find HEAD commit for this tag")
- .nav-controls.controls-flex
+ .nav-controls
- if can?(current_user, :push_code, @project)
= link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
= icon("pencil")
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index cb8a8a24be8..12eb8d7fa81 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -21,7 +21,7 @@
.search-results
- if @scope == 'projects'
.term
- = render 'shared/projects/list', projects: @search_objects, pipeline_status: false
+ = render 'shared/projects/list', { projects: @search_objects, pipeline_status: false }.merge(@display_options)
- else
- locals = { projects: blob_projects(@search_objects) } if %w[blobs wiki_blobs].include?(@scope)
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects, locals: locals
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 796782035f2..1f055cdfa31 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -1,7 +1,7 @@
.search-result-row
%h4
= confidential_icon(issue)
- = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
+ = link_to namespace_project_issue_path(issue.project.namespace.becomes(Namespace), issue.project, issue) do
%span.term.str-truncated= issue.title
- if issue.closed?
%span.badge.badge-danger.prepend-left-5= _("Closed")
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index f0e0af11f27..074bb9bce8d 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
+ = link_to namespace_project_merge_request_path(merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request) do
%span.term.str-truncated= merge_request.title
- if merge_request.merged?
%span.badge.badge-primary.prepend-left-5= _("Merged")
diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
index 2daa96e34d1..3201f1a7815 100644
--- a/app/views/search/results/_milestone.html.haml
+++ b/app/views/search/results/_milestone.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to [milestone.project.namespace.becomes(Namespace), milestone.project, milestone] do
+ = link_to namespace_project_milestone_path(milestone.project.namespace.becomes(Namespace), milestone.project, milestone) do
%span.term.str-truncated= milestone.title
- if milestone.description.present?
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index 9ec76d82d18..e83ca5eaab8 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -2,9 +2,12 @@
- group = group_link.group
- can_admin_member = can?(current_user, :admin_project_member, @project)
- dom_id = "group_member_#{group_link.id}"
-%li.member.group_member{ id: dom_id }
- %span.list-item-name
- = group_icon(group, class: "avatar s40", alt: '')
+
+-# Note this is just for groups. For individual members please see shared/members/_member
+
+%li.member.group_member.py-2.px-3.d-flex.flex-column.flex-md-row{ id: dom_id }
+ %span.list-item-name.mb-2.m-md-0
+ = group_icon(group, class: "avatar s40 flex-shrink-0 flex-grow-0", alt: '')
.user-info
= link_to group.full_name, group_path(group), class: 'member'
.cgray
@@ -13,10 +16,10 @@
·
%span{ class: ('text-warning' if group_link.expires_soon?) }
= _("Expires in %{expires_at}").html_safe % { expires_at: distance_of_time_in_words_to_now(group_link.expires_at) }
- .controls.member-controls
- = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'js-edit-member-form form-group row append-right-5' do
+ .controls.member-controls.align-items-center
+ = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'js-edit-member-form form-group d-sm-flex' do
= hidden_field_tag "group_link[group_access]", group_link.group_access
- .member-form-control.dropdown.append-right-5
+ .member-form-control.dropdown.mr-sm-2.d-sm-inline-block
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
disabled: !can_admin_member,
data: { toggle: "dropdown", field_name: "group_link[group_access]" } }
@@ -32,14 +35,14 @@
= link_to role, "javascript:void(0)",
class: ("is-active" if group_link.group_access == role_id),
data: { id: role_id, el_id: dom_id }
- .prepend-left-5.clearable-input.member-form-control
+ .clearable-input.member-form-control.d-sm-inline-block
= text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: _('Expiration date'), id: "member_expires_at_#{group.id}", disabled: !can_admin_member
%i.clear-icon.js-clear-input
- if can_admin_member
= link_to project_group_link_path(@project, group_link),
method: :delete,
data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name } },
- class: 'btn btn-remove prepend-left-10' do
+ class: 'btn btn-remove m-0 ml-sm-2 align-self-center' do
%span.d-block.d-sm-none
= _("Delete")
= icon('trash', class: 'd-none d-sm-block')
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index afcb2b71472..331283f7eec 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -6,10 +6,12 @@
- source = member.source
- override = member.try(:override)
-%li.member{ class: [dom_class(member), ("is-overridden" if override)], id: dom_id(member) }
- %span.list-item-name
+-# Note this is just for individual members. For groups please see shared/members/_group
+
+%li.member.py-2.px-3.d-flex.flex-column{ class: [dom_class(member), ("is-overridden" if override), ("flex-md-row" unless force_mobile_view)], id: dom_id(member) }
+ %span.list-item-name.mb-2.m-md-0
- if user
- = image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: ''
+ = image_tag avatar_icon_for_user(user, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
.user-info
= link_to user.name, user_path(user), class: 'member js-user-link', data: { user_id: user.id }
= user_status(user)
@@ -43,7 +45,7 @@
= _("Expires in %{expires_at}").html_safe % { expires_at: distance_of_time_in_words_to_now(member.expires_at) }
- else
- = image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40", alt: ''
+ = image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
.user-info
.member= member.invite_email
.cgray
@@ -54,20 +56,20 @@
= time_ago_with_tooltip(member.created_at)
- if show_roles
- current_resource = @project || @group
- .controls.member-controls
+ .controls.member-controls.align-items-center
= render_if_exists 'shared/members/ee/ldap_tag', can_override: member.can_override?
- if show_controls && member.source == current_resource
- if member.can_resend_invite?
= link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
method: :post,
- class: 'btn btn-default prepend-left-10 d-none d-sm-block',
+ class: 'btn btn-default align-self-center mr-sm-2',
title: _('Resend invite')
- if user != current_user && member.can_update?
- = form_for member, remote: true, html: { class: 'js-edit-member-form form-group row append-right-5' } do |f|
+ = form_for member, remote: true, html: { class: "js-edit-member-form form-group #{'d-sm-flex' unless force_mobile_view}" } do |f|
= f.hidden_field :access_level
- .member-form-control.dropdown.append-right-5
+ .member-form-control.dropdown{ class: [("mr-sm-2 d-sm-inline-block" unless force_mobile_view)] }
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
disabled: member.can_override? && !override,
data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } }
@@ -87,7 +89,7 @@
group: @group,
member: member,
can_override: member.can_override?
- .prepend-left-5.clearable-input.member-form-control
+ .clearable-input.member-form-control{ class: [("d-sm-inline-block" unless force_mobile_view)] }
= f.text_field :expires_at,
disabled: member.can_override? && !override,
class: 'form-control js-access-expiration-date js-member-update-control',
@@ -96,12 +98,12 @@
data: { el_id: dom_id(member) }
%i.clear-icon.js-clear-input
- else
- %span.member-access-text= member.human_access
+ %span.member-access-text.user-access-role= member.human_access
- if member.can_approve?
= link_to polymorphic_path([:approve_access_request, member]),
method: :post,
- class: 'btn btn-success prepend-left-10',
+ class: "btn btn-success align-self-center m-0 mb-2 #{'mb-sm-0 ml-sm-2' unless force_mobile_view}",
title: _('Grant access') do
%span{ class: ('d-block d-sm-none' unless force_mobile_view) }
= _('Grant access')
@@ -113,12 +115,12 @@
= link_to icon('sign-out', text: _('Leave')), polymorphic_path([:leave, member.source, :members]),
method: :delete,
data: { confirm: leave_confirmation_message(member.source) },
- class: 'btn btn-remove prepend-left-10'
+ class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}"
- else
= link_to member,
method: :delete,
data: { confirm: remove_member_message(member) },
- class: 'btn btn-remove prepend-left-10',
+ class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}",
title: remove_member_title(member) do
%span{ class: ('d-block d-sm-none' unless force_mobile_view) }
= _("Delete")
@@ -126,6 +128,6 @@
= icon('trash', class: 'd-none d-sm-block')
= render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :edit, can_override: member.can_override?
- else
- %span.member-access-text= member.human_access
+ %span.member-access-text.user-access-role= member.human_access
= render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :confirm, can_override: member.can_override?
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 6fec435cc87..5c9dd72418e 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -31,7 +31,7 @@
.note-header
.note-header-info
%a{ href: user_path(note.author) }
- %span.note-header-author-name
+ %span.note-header-author-name.bold
= sanitize(note.author.name)
= user_status(note.author)
%span.note-headline-light
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 6dc61088e65..a71bfd624e4 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -9,7 +9,7 @@
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
.user-profile
- .cover-block.user-cover-block.top-area
+ .cover-block.user-cover-block
.cover-controls
- if @user == current_user
= link_to profile_path, class: 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do
diff --git a/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml b/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml
new file mode 100644
index 00000000000..1297e9712fa
--- /dev/null
+++ b/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml
@@ -0,0 +1,5 @@
+---
+title: "Added code differnces from EE in file 'app/assets/javascripts/pages/projects/project.js' to CE"
+merge_request: 29271
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/37495.yml b/changelogs/unreleased/37495.yml
new file mode 100644
index 00000000000..f6d421fc45b
--- /dev/null
+++ b/changelogs/unreleased/37495.yml
@@ -0,0 +1,5 @@
+---
+title: Add documentation links for confidental and locked discussions
+merge_request: 29073
+author:
+type: changed
diff --git a/changelogs/unreleased/51636-task-list-api-pderichs.yml b/changelogs/unreleased/51636-task-list-api-pderichs.yml
new file mode 100644
index 00000000000..f18a0936ab2
--- /dev/null
+++ b/changelogs/unreleased/51636-task-list-api-pderichs.yml
@@ -0,0 +1,5 @@
+---
+title: Add task count and completed count to responses of Issue and MR
+merge_request: 28859
+author:
+type: added
diff --git a/changelogs/unreleased/55033-discussion-system-note-alignment.yml b/changelogs/unreleased/55033-discussion-system-note-alignment.yml
new file mode 100644
index 00000000000..27072ec7e12
--- /dev/null
+++ b/changelogs/unreleased/55033-discussion-system-note-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Align system note within discussion with other notes
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/55125-mr-tab-scrolling.yml b/changelogs/unreleased/55125-mr-tab-scrolling.yml
new file mode 100644
index 00000000000..e03ff6c5060
--- /dev/null
+++ b/changelogs/unreleased/55125-mr-tab-scrolling.yml
@@ -0,0 +1,5 @@
+---
+title: Update merge request tabs so they no longer scroll
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml b/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml
new file mode 100644
index 00000000000..25cc973159f
--- /dev/null
+++ b/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce Gitaly calls to improve performance when rendering suggestions
+merge_request: 29027
+author:
+type: performance
diff --git a/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml b/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml
new file mode 100644
index 00000000000..0904f788b6f
--- /dev/null
+++ b/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml
@@ -0,0 +1,5 @@
+---
+title: Change "Report abuse to GitLab" to more generic wording
+merge_request: 28884
+author: Marc Schwede
+type: other
diff --git a/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml b/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml
new file mode 100644
index 00000000000..fdf80c660f7
--- /dev/null
+++ b/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml
@@ -0,0 +1,5 @@
+---
+title: Default MR checkbox to true in most cases
+merge_request: !28665
+author:
+type: changed
diff --git a/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml b/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml
new file mode 100644
index 00000000000..90b72ec05c7
--- /dev/null
+++ b/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Replaces sidekiq mtail metrics with ruby instrumentation metrics
+merge_request: 29215
+author:
+type: changed
diff --git a/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml
new file mode 100644
index 00000000000..83b7bd3433e
--- /dev/null
+++ b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml
@@ -0,0 +1,5 @@
+---
+title: Update registration form to indicate invalid name or username length on input
+merge_request: 28095
+author: Jiaan Louw
+type: changed
diff --git a/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml b/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml
index 6f8c82c2dc8..94666ac12ec 100644
--- a/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml
+++ b/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml
@@ -1,5 +1,5 @@
---
title: Resolve Snippet icon button is misaligned
merge_request: 28522
-author: Marcel van Remmerden
+author:
type: other
diff --git a/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml b/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml
index 350fd525a30..a7f5706058d 100644
--- a/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml
+++ b/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml
@@ -1,5 +1,5 @@
---
title: Give New Snippet button green outline
merge_request: 28559
-author: Marcel van Remmerden
+author:
type: other
diff --git a/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml b/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml
index c3808709fca..e446459ffc8 100644
--- a/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml
+++ b/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml
@@ -1,5 +1,5 @@
---
title: Add hover and focus to Attach a file
merge_request: 28682
-author: Marcel van Remmerden
+author:
type: fixed
diff --git a/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml b/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml
new file mode 100644
index 00000000000..718604c9ceb
--- /dev/null
+++ b/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Merge request discussion text jumps when resolved
+merge_request: 28995
+author:
+type: fixed
diff --git a/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
index 46d3f439a44..4d2f73ce2ff 100644
--- a/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
+++ b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
@@ -1,5 +1,5 @@
---
-title: Change collapse icon size to size of profile picture
+title: Change collapse icon size to size of profile picture
merge_request: 28512
-author: Marcel van Remmerden
+author:
type: other
diff --git a/changelogs/unreleased/62091-remove-time-windows-flag.yml b/changelogs/unreleased/62091-remove-time-windows-flag.yml
new file mode 100644
index 00000000000..c6c11328312
--- /dev/null
+++ b/changelogs/unreleased/62091-remove-time-windows-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Allow users to specify a time range on metrics dashboard
+merge_request: 28670
+author:
+type: added
diff --git a/changelogs/unreleased/62124-new-threaded-discussion-design.yml b/changelogs/unreleased/62124-new-threaded-discussion-design.yml
new file mode 100644
index 00000000000..6614e05be74
--- /dev/null
+++ b/changelogs/unreleased/62124-new-threaded-discussion-design.yml
@@ -0,0 +1,5 @@
+---
+title: Implement borderless discussion design with new reply field
+merge_request: 28580
+author:
+type: added
diff --git a/changelogs/unreleased/62418-project-default-git-depth.yml b/changelogs/unreleased/62418-project-default-git-depth.yml
new file mode 100644
index 00000000000..b5647cd0859
--- /dev/null
+++ b/changelogs/unreleased/62418-project-default-git-depth.yml
@@ -0,0 +1,5 @@
+---
+title: Add project level git depth CI/CD setting
+merge_request: 28919
+author:
+type: added
diff --git a/changelogs/unreleased/62656-adjusted-dropdown-styles.yml b/changelogs/unreleased/62656-adjusted-dropdown-styles.yml
new file mode 100644
index 00000000000..36f14ae2741
--- /dev/null
+++ b/changelogs/unreleased/62656-adjusted-dropdown-styles.yml
@@ -0,0 +1,5 @@
+---
+title: "changed the styles on `Add List` dropdown to look more like the EE vesion"
+merge_request: 29338
+author: Michel Engelen
+type: changed
diff --git a/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml b/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml
new file mode 100644
index 00000000000..fca92a0d4dc
--- /dev/null
+++ b/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml
@@ -0,0 +1,5 @@
+---
+title: Include the port in the URLs of the API Link headers
+merge_request: 29267
+author:
+type: fixed
diff --git a/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml b/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml
new file mode 100644
index 00000000000..d60dd65be8a
--- /dev/null
+++ b/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml
@@ -0,0 +1,5 @@
+---
+title: Always show "Pipelines must succeed" checkbox
+merge_request: 28651
+author:
+type: fixed
diff --git a/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml b/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml
new file mode 100644
index 00000000000..a795e33b00d
--- /dev/null
+++ b/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid setting Gitlab::Session on sessionless requests and Git HTTP
+merge_request: 29146
+author:
+type: fixed
diff --git a/changelogs/unreleased/ci-variable-conjunction.yml b/changelogs/unreleased/ci-variable-conjunction.yml
new file mode 100644
index 00000000000..839c4285f3a
--- /dev/null
+++ b/changelogs/unreleased/ci-variable-conjunction.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for && and || to CI Pipeline Expressions. Change CI variable expression matching for Lexeme::Pattern to eagerly return tokens.
+merge_request: 27925
+author: Martin Manelli
+type: added
diff --git a/changelogs/unreleased/copy-button-in-modals.yml b/changelogs/unreleased/copy-button-in-modals.yml
new file mode 100644
index 00000000000..bc18eb9ab26
--- /dev/null
+++ b/changelogs/unreleased/copy-button-in-modals.yml
@@ -0,0 +1,5 @@
+---
+title: Add a New Copy Button That Works in Modals
+merge_request: 28676
+author:
+type: added
diff --git a/changelogs/unreleased/docs-add-chatops-request-doc.yml b/changelogs/unreleased/docs-add-chatops-request-doc.yml
new file mode 100644
index 00000000000..85ba86a73af
--- /dev/null
+++ b/changelogs/unreleased/docs-add-chatops-request-doc.yml
@@ -0,0 +1,5 @@
+---
+title: Add section to dev docs on accessing chatops
+merge_request: 28623
+author:
+type: other
diff --git a/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml b/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml
new file mode 100644
index 00000000000..443fff92f55
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml
@@ -0,0 +1,5 @@
+---
+title: Use to 'gitlabktl' build serverless applications
+merge_request: 29258
+author:
+type: added
diff --git a/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml b/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml
new file mode 100644
index 00000000000..46fa90ccda0
--- /dev/null
+++ b/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml
@@ -0,0 +1,5 @@
+---
+title: Allow lowercase prefix for Youtrack issue ids
+merge_request: 29057
+author: Matthias Baur
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml b/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml
new file mode 100644
index 00000000000..88656b7ef4c
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml
@@ -0,0 +1,5 @@
+---
+title: Fix serverless apps deployments by bumping 'tm' version
+merge_request: 29254
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml b/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml
new file mode 100644
index 00000000000..f51ec273a57
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml
@@ -0,0 +1,5 @@
+---
+title: Remove build policies from serverless app template
+merge_request: 29253
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-time-window-default.yml b/changelogs/unreleased/fix-time-window-default.yml
new file mode 100644
index 00000000000..147f82eb6c9
--- /dev/null
+++ b/changelogs/unreleased/fix-time-window-default.yml
@@ -0,0 +1,5 @@
+---
+title: Use the selected time window for metrics dashboard
+merge_request: 29152
+author:
+type: fixed
diff --git a/changelogs/unreleased/gt-externalize-profiles-preferences.yml b/changelogs/unreleased/gt-externalize-profiles-preferences.yml
new file mode 100644
index 00000000000..1a72e92a241
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-profiles-preferences.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize profiles preferences
+merge_request: 28470
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/improve-email-text-part.yml b/changelogs/unreleased/improve-email-text-part.yml
new file mode 100644
index 00000000000..ce506cb1507
--- /dev/null
+++ b/changelogs/unreleased/improve-email-text-part.yml
@@ -0,0 +1,5 @@
+---
+title: Improve new user email markup unconsistency between text and html parts
+merge_request: 29111
+author: Haunui Saint-sevin
+type: fixed
diff --git a/changelogs/unreleased/sh-default-visibility-fix.yml b/changelogs/unreleased/sh-default-visibility-fix.yml
new file mode 100644
index 00000000000..8308f310150
--- /dev/null
+++ b/changelogs/unreleased/sh-default-visibility-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Set project default visibility to max allowed
+merge_request: 28754
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-55869.yml b/changelogs/unreleased/sh-fix-issue-55869.yml
new file mode 100644
index 00000000000..7935cffc13b
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-55869.yml
@@ -0,0 +1,5 @@
+---
+title: Fix alignment of resend button in members page
+merge_request: 28202
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-openid-connect-defaults.yml b/changelogs/unreleased/sh-fix-openid-connect-defaults.yml
new file mode 100644
index 00000000000..1ed977c9be6
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-openid-connect-defaults.yml
@@ -0,0 +1,5 @@
+---
+title: Make OpenID Connect work without requiring a name
+merge_request: 29312
+author:
+type: fixed
diff --git a/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml b/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml
new file mode 100644
index 00000000000..3391fcc9537
--- /dev/null
+++ b/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml
@@ -0,0 +1,5 @@
+---
+title: Allow masking if 8 or more characters in base64.
+merge_request: 29143
+author: thomas-nilsson-irfu
+type: changed
diff --git a/changelogs/unreleased/update-gitlab-shell-9-3-0.yml b/changelogs/unreleased/update-gitlab-shell-9-3-0.yml
new file mode 100644
index 00000000000..781ff31c7d8
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-shell-9-3-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update to GitLab Shell v9.3.0
+merge_request: 29283
+author:
+type: other
diff --git a/changelogs/unreleased/update-psd-doc.yml b/changelogs/unreleased/update-psd-doc.yml
new file mode 100644
index 00000000000..38c8d1c0d68
--- /dev/null
+++ b/changelogs/unreleased/update-psd-doc.yml
@@ -0,0 +1,5 @@
+---
+title: Update project security dashboard documentation
+merge_request: 28681
+author:
+type: changed
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 494ddd72556..ac9b02b08d5 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -47,4 +47,7 @@ Rails.application.configure do
config.assets.quiet = true
config.allow_concurrency = defined?(::Puma)
+
+ # BetterErrors live shell (REPL) on every stack frame
+ BetterErrors::Middleware.allow_ip!("127.0.0.1/0")
end
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 68f8487d377..4da683014d4 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -19,12 +19,6 @@ Gitlab::Application.configure do |config|
config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware)
end
-Sidekiq.configure_server do |config|
- config.on(:startup) do
- Gitlab::Metrics::SidekiqMetricsExporter.instance.start
- end
-end
-
if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
Gitlab::Cluster::LifecycleEvents.on_worker_start do
defined?(::Prometheus::Client.reinitialize_on_pid_change) && Prometheus::Client.reinitialize_on_pid_change
diff --git a/config/routes.rb b/config/routes.rb
index f5957f43655..cb90a0134c4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -75,6 +75,8 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update]
end
+ get 'acme-challenge/' => 'acme_challenges#show'
+
# UserCallouts
resources :user_callouts, only: [:create]
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index 048c539bcf9..bdb4343b1d6 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -163,10 +163,10 @@ def lint_commit(commit)
end
if emoji_checker.includes_emoji?(commit.message)
- fail_commit(
+ warn_commit(
commit,
'Avoid the use of Markdown Emoji such as `:+1:`. ' \
- 'These add no value to the commit message, ' \
+ 'These add limited value to the commit message, ' \
'and are displayed as plain text outside of GitLab'
)
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
index de314c5b39f..a0f1447e76a 100644
--- a/danger/roulette/Dangerfile
+++ b/danger/roulette/Dangerfile
@@ -33,10 +33,14 @@ MARKDOWN
def spin_for_category(team, project, category, branch_name)
random = roulette.new_random(branch_name)
+ labels = gitlab.mr_labels
- reviewers = team.select { |member| member.reviewer?(project, category) }
- traintainers = team.select { |member| member.traintainer?(project, category) }
- maintainers = team.select { |member| member.maintainer?(project, category) }
+ reviewers, traintainers, maintainers =
+ %i[reviewer? traintainer? maintainer?].map do |kind|
+ team.select do |member|
+ member.public_send(kind, project, category, labels) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
# TODO: take CODEOWNERS into account?
# https://gitlab.com/gitlab-org/gitlab-ce/issues/57653
diff --git a/db/migrate/20190429082448_create_pages_domain_acme_orders.rb b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
new file mode 100644
index 00000000000..af811e83518
--- /dev/null
+++ b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
@@ -0,0 +1,28 @@
+# 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 CreatePagesDomainAcmeOrders < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :pages_domain_acme_orders do |t|
+ t.references :pages_domain, null: false, index: true, foreign_key: { on_delete: :cascade }, type: :integer
+
+ t.datetime_with_timezone :expires_at, null: false
+ t.timestamps_with_timezone null: false
+
+ t.string :url, null: false
+
+ t.string :challenge_token, null: false, index: true
+ t.text :challenge_file_content, null: false
+
+ t.text :encrypted_private_key, null: false
+ t.text :encrypted_private_key_iv, null: false
+ end
+ end
+end
diff --git a/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb b/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb
new file mode 100644
index 00000000000..18544dcb6d3
--- /dev/null
+++ b/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb
@@ -0,0 +1,16 @@
+# 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 AddSslValidPeriodToPagesDomain < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :pages_domains, :certificate_valid_not_before, :datetime_with_timezone
+ add_column :pages_domains, :certificate_valid_not_after, :datetime_with_timezone
+ end
+end
diff --git a/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb b/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb
new file mode 100644
index 00000000000..8abea05def4
--- /dev/null
+++ b/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddDefaultGitDepthToCiCdSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :project_ci_cd_settings, :default_git_depth, :integer
+ end
+end
diff --git a/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb b/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb
new file mode 100644
index 00000000000..1d8510e4514
--- /dev/null
+++ b/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb
@@ -0,0 +1,34 @@
+# 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 ScheduleFillValidTimeForPagesDomainCertificates < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ MIGRATION = 'FillValidTimeForPagesDomainCertificate'
+ BATCH_SIZE = 500
+ BATCH_TIME = 5.minutes
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class PagesDomain < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'pages_domains'
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ PagesDomain.where.not(certificate: [nil, '']),
+ MIGRATION,
+ BATCH_TIME,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fcf9e397ac1..22d0cddb7a0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1571,6 +1571,20 @@ ActiveRecord::Schema.define(version: 20190530154715) do
t.index ["access_grant_id"], name: "index_oauth_openid_requests_on_access_grant_id", using: :btree
end
+ create_table "pages_domain_acme_orders", force: :cascade do |t|
+ t.integer "pages_domain_id", null: false
+ t.datetime_with_timezone "expires_at", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.string "url", null: false
+ t.string "challenge_token", null: false
+ t.text "challenge_file_content", null: false
+ t.text "encrypted_private_key", null: false
+ t.text "encrypted_private_key_iv", null: false
+ t.index ["challenge_token"], name: "index_pages_domain_acme_orders_on_challenge_token", using: :btree
+ t.index ["pages_domain_id"], name: "index_pages_domain_acme_orders_on_pages_domain_id", using: :btree
+ end
+
create_table "pages_domains", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.text "certificate"
@@ -1583,6 +1597,8 @@ ActiveRecord::Schema.define(version: 20190530154715) do
t.datetime_with_timezone "enabled_until"
t.datetime_with_timezone "remove_at"
t.boolean "auto_ssl_enabled", default: false, null: false
+ t.datetime_with_timezone "certificate_valid_not_before"
+ t.datetime_with_timezone "certificate_valid_not_after"
t.index ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
t.index ["project_id", "enabled_until"], name: "index_pages_domains_on_project_id_and_enabled_until", using: :btree
t.index ["project_id"], name: "index_pages_domains_on_project_id", using: :btree
@@ -1644,6 +1660,7 @@ ActiveRecord::Schema.define(version: 20190530154715) do
t.boolean "group_runners_enabled", default: true, null: false
t.boolean "merge_pipelines_enabled"
t.boolean "merge_trains_enabled", default: false, null: false
+ t.integer "default_git_depth"
t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true, using: :btree
end
@@ -2560,6 +2577,7 @@ ActiveRecord::Schema.define(version: 20190530154715) do
add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade
add_foreign_key "notification_settings", "users", name: "fk_0c95e91db7", on_delete: :cascade
add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
+ add_foreign_key "pages_domain_acme_orders", "pages_domains", on_delete: :cascade
add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade
add_foreign_key "personal_access_tokens", "users"
add_foreign_key "pool_repositories", "projects", column: "source_project_id", on_delete: :nullify
diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md
index e55f7dbb4df..00422ec347c 100644
--- a/doc/administration/auth/oidc.md
+++ b/doc/administration/auth/oidc.md
@@ -31,6 +31,7 @@ The OpenID Connect will provide you with a client details and secret for you to
{ 'name' => 'openid_connect',
'label' => '<your_oidc_label>',
'args' => {
+ "name' => 'openid_connect',
'scope' => ['openid','profile'],
'response_type' => 'code',
'issuer' => '<your_oidc_url>',
@@ -53,6 +54,7 @@ The OpenID Connect will provide you with a client details and secret for you to
- { name: 'openid_connect',
label: '<your_oidc_label>',
args: {
+ name: 'openid_connect',
scope: ['openid','profile'],
response_type: 'code',
issuer: '<your_oidc_url>',
@@ -103,3 +105,59 @@ On the sign in page, there should now be an OpenID Connect icon below the regula
Click the icon to begin the authentication process. The OpenID Connect provider will ask the user to
sign in and authorize the GitLab application (if confirmation required by the client). If everything goes well, the user
will be redirected to GitLab and will be signed in.
+
+## Example configurations
+
+The following configurations illustrate how to set up OpenID with
+different providers with Omnibus GitLab.
+
+### Google
+
+See the [Google
+documentation](https://developers.google.com/identity/protocols/OpenIDConnect)
+for more details:
+
+```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ 'name' => 'openid_connect',
+ 'label' => 'Google OpenID',
+ 'args' => {
+ 'name' => 'openid_connect',
+ 'scope' => ['openid', 'profile', 'email'],
+ 'response_type' => 'code',
+ 'issuer' => 'https://accounts.google.com',
+ 'client_auth_method' => 'query',
+ 'discovery' => true,
+ 'uid_field' => 'preferred_username',
+ 'client_options' => {
+ 'identifier' => '<YOUR PROJECT CLIENT ID>',
+ 'secret' => '<YOUR PROJECT CLIENT SECRET>',
+ 'redirect_uri' => 'https://example.com/users/auth/openid_connect/callback',
+ }
+ }
+ }
+```
+
+### Troubleshooting
+
+If you're having trouble, here are some tips:
+
+1. Ensure `discovery` is set to `true`. Setting it to `false` requires
+specifying all the URLs and keys required to make OpenID work.
+
+1. Check your system clock to ensure the time is synchronized properly.
+
+1. As mentioned in [the
+documentation](https://github.com/m0n9oose/omniauth_openid_connect),
+make sure `issuer` corresponds to the base URL of the Discovery URL. For
+example, `https://accounts.google.com` is used for the URL
+`https://accounts.google.com/.well-known/openid-configuration`.
+
+1. The OpenID Connect client uses HTTP Basic Authentication to send the
+OAuth2 access token. For example, if you are seeing 401 errors upon
+retrieving the `userinfo` endpoint, you may want to check your OpenID
+Web server configuration. For example, for
+[oauth2-server-php](https://github.com/bshaffer/oauth2-server-php), you
+may need to [add a configuration parameter to
+Apache](https://github.com/bshaffer/oauth2-server-php/issues/926#issuecomment-387502778).
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index dce3819a7c1..3b874e5d312 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -23,10 +23,10 @@ environments including [Basic Scaling](README.md#basic-scaling) and
### Provide your own PostgreSQL instance **[CORE ONLY]**
-If you want to use your own deployed PostgreSQL instance(s),
+If you want to use your own deployed PostgreSQL instance(s),
see [Provide your own PostgreSQL instance](#provide-your-own-postgresql-instance-core-only)
-for more details. However, you can use the GitLab Omnibus package to easily
-deploy the bundled PostgreSQL.
+for more details. However, you can use the GitLab Omnibus package to easily
+deploy the bundled PostgreSQL.
### Standalone PostgreSQL using GitLab Omnibus **[CORE ONLY]**
@@ -36,19 +36,19 @@ deploy the bundled PostgreSQL.
- Do not complete any other steps on the download page.
1. Generate a password hash for PostgreSQL. This assumes you will use the default
username of `gitlab` (recommended). The command will request a password
- and confirmation. Use the value that is output by this command in the next
+ and confirmation. Use the value that is output by this command in the next
step as the value of `POSTGRESQL_PASSWORD_HASH`.
```sh
sudo gitlab-ctl pg-password-md5 gitlab
```
-
+
1. Edit `/etc/gitlab/gitlab.rb` and add the contents below, updating placeholder
- values appropriately.
-
+ values appropriately.
+
- `POSTGRESQL_PASSWORD_HASH` - The value output from the previous step
- `APPLICATION_SERVER_IP_BLOCKS` - A space delimited list of IP subnets or IP
- addresses of the GitLab application servers that will connect to the
+ addresses of the GitLab application servers that will connect to the
database. Example: `%w(123.123.123.123/32 123.123.123.234/32)`
```ruby
@@ -65,11 +65,11 @@ deploy the bundled PostgreSQL.
postgresql['listen_address'] = '0.0.0.0'
postgresql['port'] = 5432
- # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
# Replace XXX.XXX.XXX.XXX/YY with Network Address
- # ????
+ # ????
postgresql['trust_auth_cidr_addresses'] = %w(APPLICATION_SERVER_IP_BLOCKS)
# Disable automatic database migrations
@@ -77,12 +77,12 @@ deploy the bundled PostgreSQL.
```
NOTE: **Note:** The role `postgres_role` was introduced with GitLab 10.3
-
+
1. [Reconfigure GitLab] for the changes to take effect.
1. Note the PostgreSQL node's IP address or hostname, port, and
plain text password. These will be necessary when configuring the GitLab
application servers later.
-
+
Advanced configuration options are supported and can be added if
needed.
@@ -101,18 +101,19 @@ environments including [Horizontal](README.md#horizontal),
If you want to use your own deployed PostgreSQL instance(s),
see [Provide your own PostgreSQL instance](#provide-your-own-postgresql-instance-core-only)
for more details. However, you can use the GitLab Omnibus package to easily
-deploy the bundled PostgreSQL.
+deploy the bundled PostgreSQL.
### High Availability with GitLab Omnibus **[PREMIUM ONLY]**
> Important notes:
+>
> - This document will focus only on configuration supported with [GitLab Premium](https://about.gitlab.com/pricing/), using the Omnibus GitLab package.
> - If you are a Community Edition or Starter user, consider using a cloud hosted solution.
> - This document will not cover installations from source.
>
> - If HA setup is not what you were looking for, see the [database configuration document](http://docs.gitlab.com/omnibus/settings/database.html)
> for the Omnibus GitLab packages.
-
+>
> Please read this document fully before attempting to configure PostgreSQL HA
> for GitLab.
>
@@ -122,9 +123,9 @@ The recommended configuration for a PostgreSQL HA requires:
- A minimum of three database nodes
- Each node will run the following services:
- - `PostgreSQL` - The database itself
- - `repmgrd` - A service to monitor, and handle failover in case of a failure
- - `Consul` agent - Used for service discovery, to alert other nodes when failover occurs
+ - `PostgreSQL` - The database itself
+ - `repmgrd` - A service to monitor, and handle failover in case of a failure
+ - `Consul` agent - Used for service discovery, to alert other nodes when failover occurs
- A minimum of three `Consul` server nodes
- A minimum of one `pgbouncer` service node
@@ -142,7 +143,7 @@ Database nodes run two services with PostgreSQL:
- Selecting a new master for the cluster.
- Promoting the new node to master.
- Instructing remaining servers to follow the new master node.
-
+
On failure, the old master node is automatically evicted from the cluster, and should be rejoined manually once recovered.
- Consul. Monitors the status of each node in the database cluster and tracks its health in a service definition on the consul cluster.
@@ -171,13 +172,10 @@ Similarly, PostgreSQL access is controlled based on the network source.
This is why you will need:
-> IP address of each nodes network interface
-> - This can be set to `0.0.0.0` to listen on all interfaces. It cannot
-> be set to the loopack address `127.0.0.1`
->
-> Network Address
-> - This can be in subnet (i.e. `192.168.0.0/255.255.255.0`) or CIDR (i.e.
-> `192.168.0.0/24`) form.
+- IP address of each nodes network interface. This can be set to `0.0.0.0` to
+ listen on all interfaces. It cannot be set to the loopack address `127.0.0.1`.
+- Network Address. This can be in subnet (i.e. `192.168.0.0/255.255.255.0`)
+ or CIDR (i.e. `192.168.0.0/24`) form.
##### User information
@@ -199,7 +197,7 @@ When using default setup, minimum configuration requires:
sudo gitlab-ctl pg-password-md5 CONSUL_USERNAME
```
-- `CONSUL_SERVER_NODES`. The IP addresses or DNS records of the Consul server nodes.
+- `CONSUL_SERVER_NODES`. The IP addresses or DNS records of the Consul server nodes.
Few notes on the service itself:
@@ -220,8 +218,7 @@ the number of database nodes in the cluster.
This is used to prevent replication from using up all of the
available database connections.
-> Note:
-> - In this document we are assuming 3 database nodes, which makes this configuration:
+In this document we are assuming 3 database nodes, which makes this configuration:
```
postgresql['max_wal_senders'] = 4
@@ -277,7 +274,7 @@ be allowed to authenticate with the service.
Few notes on the service itself:
- The service runs under the same system account as the database
- - In the package, this is by default `gitlab-psql`
+ - In the package, this is by default `gitlab-psql`
- The service will have a superuser database user account generated for it
- This defaults to `gitlab_repmgr`
@@ -327,7 +324,7 @@ On each Consul node perform the following:
Before moving on, make sure Consul is configured correctly. Run the following
command to verify all server nodes are communicating:
-```
+```sh
/opt/gitlab/embedded/bin/consul members
```
@@ -401,14 +398,15 @@ check the [Troubleshooting section](#troubleshooting) before proceeding.
repmgr['master_on_initialization'] = false
```
-1. [Reconfigure GitLab] for te changes to take effect.
+1. [Reconfigure GitLab] for the changes to take effect.
> Please note:
+>
> - If you want your database to listen on a specific interface, change the config:
-> `postgresql['listen_address'] = '0.0.0.0'`
+> `postgresql['listen_address'] = '0.0.0.0'`.
> - If your Pgbouncer service runs under a different user account,
> you also need to specify: `postgresql['pgbouncer_user'] = PGBOUNCER_USERNAME` in
-> your configuration
+> your configuration.
##### Database nodes post-configuration
@@ -449,7 +447,6 @@ Select one node as a primary node.
is not an IP address, it will need to be a resolvable name (via DNS or
`/etc/hosts`)
-
###### Secondary nodes
1. Set up the repmgr standby:
@@ -500,7 +497,7 @@ Before moving on, make sure the databases are configured correctly. Run the
following command on the **primary** node to verify that replication is working
properly:
-```
+```sh
gitlab-ctl repmgr cluster show
```
@@ -518,7 +515,7 @@ If the 'Role' column for any node says "FAILED", check the
Also, check that the check master command works successfully on each node:
-```
+```sh
su - gitlab-consul
gitlab-ctl repmgr-check-master || echo 'This node is a standby repmgr node'
```
@@ -649,7 +646,7 @@ in the Troubleshooting section before proceeding.
##### Ensure GitLab is running
At this point, your GitLab instance should be up and running. Verify you are
-able to login, and create issues and merge requests. If you have troubles check
+able to login, and create issues and merge requests. If you have troubles check
the [Troubleshooting section](#troubleshooting).
#### Example configuration
@@ -665,13 +662,13 @@ can connect to each freely other on those addresses.
Here is a list and description of each machine and the assigned IP:
-* `10.6.0.11`: Consul 1
-* `10.6.0.12`: Consul 2
-* `10.6.0.13`: Consul 3
-* `10.6.0.21`: PostgreSQL master
-* `10.6.0.22`: PostgreSQL secondary
-* `10.6.0.23`: PostgreSQL secondary
-* `10.6.0.31`: GitLab application
+- `10.6.0.11`: Consul 1
+- `10.6.0.12`: Consul 2
+- `10.6.0.13`: Consul 3
+- `10.6.0.21`: PostgreSQL master
+- `10.6.0.22`: PostgreSQL secondary
+- `10.6.0.23`: PostgreSQL secondary
+- `10.6.0.31`: GitLab application
All passwords are set to `toomanysecrets`, please do not use this password or derived hashes.
@@ -735,7 +732,7 @@ consul['configuration'] = {
On secondary nodes, edit `/etc/gitlab/gitlab.rb` and add all the configuration
added to primary node, noted above. In addition, append the following
-configuration
+configuration:
```
# HA setting to specify if a node should attempt to be master on initialization
@@ -839,10 +836,10 @@ In this example we start with all servers on the same 10.6.0.0/16 private networ
Here is a list and description of each machine and the assigned IP:
-* `10.6.0.21`: PostgreSQL master
-* `10.6.0.22`: PostgreSQL secondary
-* `10.6.0.23`: PostgreSQL secondary
-* `10.6.0.31`: GitLab application
+- `10.6.0.21`: PostgreSQL master
+- `10.6.0.22`: PostgreSQL secondary
+- `10.6.0.23`: PostgreSQL secondary
+- `10.6.0.31`: GitLab application
All passwords are set to `toomanysecrets`, please do not use this password or derived hashes.
@@ -853,6 +850,7 @@ Please note that after the initial configuration, if a failover occurs, the Post
##### Example minimal configuration for database servers
##### Primary node
+
On primary database node edit `/etc/gitlab/gitlab.rb`:
```ruby
@@ -1047,7 +1045,6 @@ For example:
repmgr['trust_auth_cidr_addresses'] = %w(192.168.1.44/32 db2.example.com)
```
-
##### MD5 Authentication
If you are running on an untrusted network, repmgr can use md5 authentication
@@ -1114,7 +1111,7 @@ steps to fix the problem:
1. Change to the `gitlab-consul` user - `su - gitlab-consul`
1. Try the check command again - `gitlab-ctl repmgr-check-master`.
-Now there should not be errors. If errors still occur then there is another problem.
+Now there should not be errors. If errors still occur then there is another problem.
#### PGBouncer error `ERROR: pgbouncer cannot connect to server`
@@ -1157,8 +1154,6 @@ If you're running into an issue with a component not outlined here, be sure to c
**Note**: We recommend that you follow the instructions here for a full [PostgreSQL cluster](#high-availability-with-gitlab-omnibus-premium-only).
If you are reading this section due to an old bookmark, you can find that old documentation [in the repository](https://gitlab.com/gitlab-org/gitlab-ce/blob/v10.1.4/doc/administration/high_availability/database.md#configure-using-omnibus).
----
-
Read more on high-availability configuration:
1. [Configure Redis](redis.md)
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index ef370573a98..05e15fc303b 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -7,9 +7,9 @@
> - Starting with GitLab 8.17, builds are renamed to jobs.
> - This is the administration documentation. For the user guide see [pipelines/job_artifacts](../user/project/pipelines/job_artifacts.md).
-Artifacts is a list of files and directories which are attached to a job
-after it completes successfully. This feature is enabled by default in all
-GitLab installations. Keep reading if you want to know how to disable it.
+Artifacts is a list of files and directories which are attached to a job after it
+finishes. This feature is enabled by default in all GitLab installations. Keep reading
+if you want to know how to disable it.
## Disabling job artifacts
@@ -42,8 +42,9 @@ To disable artifacts site-wide, follow the steps below.
## Storing job artifacts
-After a successful job, GitLab Runner uploads an archive containing the job
-artifacts to GitLab.
+GitLab Runner can upload an archive containing the job artifacts to GitLab. By default,
+this is done when the job succeeds, but can also be done on failure, or always, via the
+[`artifacts:when`](../ci/yaml/README.md#artifactswhen) parameter.
### Using local storage
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index ac41f9177dd..c5cfb8d5016 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -129,7 +129,7 @@ It contains information about [integrations](../user/project/integrations/projec
``` json
{"severity":"ERROR","time":"2018-09-06T14:56:20.439Z","service_class":"JiraService","project_id":8,"project_path":"h5bp/html5-boilerplate","message":"Error sending message","client_url":"http://jira.gitlap.com:8080","error":"execution expired"}
-{"severity":"INFO","time":"2018-09-06T17:15:16.365Z","service_class":"JiraService","project_id":3,"project_path":"namespace2/project2","message":"Successfully posted","client_url":"http://jira.example.net"}
+{"severity":"INFO","time":"2018-09-06T17:15:16.365Z","service_class":"JiraService","project_id":3,"project_path":"namespace2/project2","message":"Successfully posted","client_url":"http://jira.example.com"}
```
## `kubernetes.log`
diff --git a/doc/api/README.md b/doc/api/README.md
index 439f58c1253..3a1064b787e 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -21,72 +21,83 @@ See also:
The following API resources are available in the project context:
-| Resource | Available endpoints |
-|:------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
-| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
-| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
-| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
-| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
-| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
-| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
-| [Deployments](deployments.md) | `/projects/:id/deployments` |
-| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` |
-| [Environments](environments.md) | `/projects/:id/environments` |
-| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
-| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
-| [Issue boards](boards.md) | `/projects/:id/boards` |
-| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
-| [Labels](labels.md) | `/projects/:id/labels` |
-| [Members](members.md) | `/projects/:id/members` (also available for groups) |
-| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
-| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` |
-| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
-| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
-| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
-| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
-| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
-| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
-| [Project badges](project_badges.md) | `/projects/:id/badges` |
-| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
-| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
-| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
-| [Project milestones](milestones.md) | `/projects/:id/milestones` |
-| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
-| [Project templates](project_templates.md) | `/projects/:id/templates` |
-| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
-| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
-| [Releases](releases/index.md) | `/projects/:id/releases` |
-| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
-| [Repositories](repositories.md) | `/projects/:id/repository` |
-| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
-| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
-| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` |
-| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
-| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
-| [Services](services.md) | `/projects/:id/services` |
-| [Tags](tags.md) | `/projects/:id/repository/tags` |
-| [Wikis](wikis.md) | `/projects/:id/wikis` |
+| Resource | Available endpoints |
+|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
+| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
+| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
+| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
+| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
+| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
+| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
+| [Deployments](deployments.md) | `/projects/:id/deployments` |
+| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) |
+| [Environments](environments.md) | `/projects/:id/environments` |
+| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
+| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
+| [Issue boards](boards.md) | `/projects/:id/boards` |
+| [Issue links](issue_links.md) **[STARTER]** | `/projects/:id/issues/.../links` |
+| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
+| [Labels](labels.md) | `/projects/:id/labels` |
+| [Managed licenses](managed_licenses.md) **[ULTIMATE]** | `/projects/:id/managed_licenses` |
+| [Members](members.md) | `/projects/:id/members` (also available for groups) |
+| [Merge request approvals](merge_request_approvals.md) **[STARTER]** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` |
+| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
+| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) |
+| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
+| [Packages](packages.md) **[PREMIUM]** | `/projects/:id/packages` |
+| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
+| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
+| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
+| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
+| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
+| [Project badges](project_badges.md) | `/projects/:id/badges` |
+| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
+| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
+| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
+| [Project milestones](milestones.md) | `/projects/:id/milestones` |
+| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
+| [Project templates](project_templates.md) | `/projects/:id/templates` |
+| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
+| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
+| [Releases](releases/index.md) | `/projects/:id/releases` |
+| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
+| [Repositories](repositories.md) | `/projects/:id/repository` |
+| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
+| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
+| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) |
+| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
+| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
+| [Services](services.md) | `/projects/:id/services` |
+| [Tags](tags.md) | `/projects/:id/repository/tags` |
+| [Vulnerabilities](vulnerabilities.md) **[ULTIMATE]** | `/projects/:id/vulnerabilities` (also available for groups) |
+| [Wikis](wikis.md) | `/projects/:id/wikis` |
### Group resources
The following API resources are available in the group context:
-| Resource | Available endpoints |
-|:--------------------------------------------------|:---------------------------------------------------------------------------------|
-| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
-| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
-| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
-| [Group badges](group_badges.md) | `/groups/:id/badges` |
-| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
-| [Group labels](group_labels.md) | `/groups/:id/labels` |
-| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
-| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
-| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
-| [Members](members.md) | `/groups/:id/members` (also available for projects) |
-| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
-| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
-| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
+| Resource | Available endpoints |
+|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
+| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
+| [Discussions](discussions.md) (threaded comments) **[ULTIMATE]** | `/groups/:id/epics/.../discussions` (also available for projects) |
+| [Epic issues](epic_issues.md) **[ULTIMATE]** | `/groups/:id/epics/.../issues` |
+| [Epic links](epic_links.md) **[ULTIMATE]** | `/groups/:id/epics/.../epics` |
+| [Epics](epics.md) **[ULTIMATE]** | `/groups/:id/epics` |
+| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
+| [Group badges](group_badges.md) | `/groups/:id/badges` |
+| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
+| [Group labels](group_labels.md) | `/groups/:id/labels` |
+| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
+| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
+| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
+| [Members](members.md) | `/groups/:id/members` (also available for projects) |
+| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
+| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) |
+| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
+| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) |
+| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
### Standalone resources
@@ -102,9 +113,11 @@ The following API resources are available outside of project and group contexts
| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) |
| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) |
| [Feature flags](features.md) | `/features` |
+| [Geo Nodes](geo_nodes.md) **[PREMIUM ONLY]** | `/geo_nodes` |
| [Import repository from GitHub](import.md) | `/import/github` |
| [Issues](issues.md) | `/issues` (also available for groups and projects) |
| [Keys](keys.md) | `/keys` |
+| [License](license.md) **[CORE ONLY]** | `/license` |
| [Markdown](markdown.md) | `/markdown` |
| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
| [Namespaces](namespaces.md) | `/namespaces` |
@@ -131,6 +144,11 @@ Endpoints are available for:
- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md).
- [Open source license templates](templates/licenses.md).
+## SCIM **[SILVER ONLY]**
+
+[GitLab.com Silver and above](https://about.gitlab.com/pricing/) provides an [SCIM API](scim.md) that implements [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644) and provides
+the `/Users` endpoint. The base URL is: `/api/scim/v2/groups/:group_path/Users/`.
+
## Road to GraphQL
Going forward, we will start on moving to
@@ -186,8 +204,6 @@ curl "https://gitlab.example.com/api/v4/projects"
The API uses JSON to serialize data. You don't need to specify `.json` at the
end of an API URL.
-All of the API endpoints that use the `POST`, `PUT` or `PATCH` method support params in the request body, with `Content-Type` `application/x-www-form-urlencoded`, `multipart/form-data` or `application/json`.
-
## Authentication
Most API requests require authentication, or will only return public data when
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 4fb3626f637..0d96cfa1b21 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -135,7 +135,11 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/1/issues/76/award_emoji",
"project":"http://example.com/api/v4/projects/1"
},
- "subscribed": false
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -265,7 +269,11 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
- "subscribed": false
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -403,7 +411,11 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
- "subscribed": false
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -500,6 +512,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -583,6 +599,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -674,6 +694,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -780,6 +804,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -865,6 +893,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -931,7 +963,11 @@ Example response:
"due_date": null,
"web_url": "http://example.com/example/example/issues/12",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1029,7 +1065,11 @@ Example response:
"due_date": null,
"web_url": "http://example.com/example/example/issues/110",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
},
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
"body": "Vel voluptas atque dicta mollitia adipisci qui at.",
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index d51451b9c83..72973b69117 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -346,30 +346,58 @@ Example of response
> **Notes**:
>
> - [Introduced][ce-2893] in GitLab 8.5.
+> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
+> in [GitLab Premium][ee] 9.5.
-Get job artifacts of a project.
+Get the job's artifacts zipped archive of a project.
```
GET /projects/:id/jobs/:job_id/artifacts
```
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
-| `job_id` | integer | yes | ID of a job. |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
+| `job_token` **[PREMIUM]** | string | no | To be used with [triggers] for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
-Example requests:
+Example request using the `PRIVATE-TOKEN` header:
```sh
-curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
+curl --output artifacts.zip --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"
```
+To use this in a [`script` definition](../ci/yaml/README.md#script) inside
+`.gitlab-ci.yml` **[PREMIUM]**, you can use either:
+
+- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the job with ID
+ `42`. Note that the command is wrapped into single quotes since it contains a
+ colon (`:`):
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"'
+ ```
+
+- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the job with ID `42`:
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts?job_token=$CI_JOB_TOKEN"'
+ ```
+
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
-| 200 | Serves the artifacts file |
-| 404 | Build not found or no artifacts |
+| 200 | Serves the artifacts file. |
+| 404 | Build not found or no artifacts.|
[ce-2893]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2893
@@ -378,9 +406,13 @@ Possible response status codes:
> **Notes**:
>
> - [Introduced][ce-5347] in GitLab 8.10.
+> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
+> in [GitLab Premium][ee] 9.5.
-Download the artifacts archive from the given reference name and job provided the
-job finished successfully.
+Download the artifacts zipped archive from the given reference name and job,
+provided the job finished successfully. This is the same as
+[getting the job's artifacts](#get-job-artifacts), but by defining the job's
+name instead of its ID.
```
GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
@@ -388,24 +420,51 @@ GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
Parameters
-| Attribute | Type | Required | Description |
-|------------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
-| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
-| `job` | string | yes | The name of the job. |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
+| `job` | string | yes | The name of the job. |
+| `job_token` **[PREMIUM]** | string | no | To be used with [triggers] for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
-Example requests:
+Example request using the `PRIVATE-TOKEN` header:
```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
```
+To use this in a [`script` definition](../ci/yaml/README.md#script) inside
+`.gitlab-ci.yml` **[PREMIUM]**, you can use either:
+
+- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the `test` job
+ of the `master` branch. Note that the command is wrapped into single quotes
+ since it contains a colon (`:`):
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test"'
+ ```
+
+- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the `test` job
+ of the `master` branch:
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"'
+ ```
+
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
-| 200 | Serves the artifacts file |
-| 404 | Build not found or no artifacts |
+| 200 | Serves the artifacts file. |
+| 404 | Build not found or no artifacts.|
[ce-5347]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5347
@@ -414,7 +473,7 @@ Possible response status codes:
> Introduced in GitLab 10.0
Download a single artifact file from a job with a specified ID from within
-the job's artifacts archive. The file is extracted from the archive and
+the job's artifacts zipped archive. The file is extracted from the archive and
streamed to the client.
```
@@ -783,3 +842,7 @@ Example of response
"user": null
}
```
+
+[ee]: https://about.gitlab.com/pricing/
+[ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346
+[triggers]: ../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline-premium
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 9529a9ec1f5..96a956ad03a 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -138,7 +138,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -280,7 +284,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -410,7 +418,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -545,7 +557,11 @@ Parameters:
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
"diverged_commits_count": 2,
- "rebase_in_progress": false
+ "rebase_in_progress": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -579,7 +595,7 @@ Parameters:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=80&d=identicon",
"web_url": "http://localhost/user2"
- },
+ }
]
```
@@ -702,7 +718,11 @@ Parameters:
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ },
"changes": [
{
"old_path": "VERSION",
@@ -865,7 +885,11 @@ POST /projects/:id/merge_requests
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1002,7 +1026,11 @@ Must include at least one non-required attribute from above.
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1155,13 +1183,17 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
## Returns the up to date merge-ref HEAD commit
-Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge`
+Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge`
ref, of the target project repository, if possible. This ref will have the state the target branch would have if
a regular merge action was taken.
@@ -1309,7 +1341,11 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1345,7 +1381,7 @@ If the rebase operation is ongoing, the response will include the following:
```json
{
- "rebase_in_progress": true
+ "rebase_in_progress": true,
"merge_error": null
}
```
@@ -1356,7 +1392,7 @@ the following:
```json
{
"rebase_in_progress": false,
- "merge_error": null,
+ "merge_error": null
}
```
@@ -1365,7 +1401,7 @@ If the rebase operation fails, the response will include the following:
```json
{
"rebase_in_progress": false,
- "merge_error": "Rebase failed. Please rebase locally",
+ "merge_error": "Rebase failed. Please rebase locally"
}
```
@@ -1572,7 +1608,11 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1701,7 +1741,11 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
index c831cc52a93..327781f6c93 100644
--- a/doc/api/project_clusters.md
+++ b/doc/api/project_clusters.md
@@ -167,6 +167,7 @@ Parameters:
| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
| `platform_kubernetes_attributes[namespace]` | String | no | The unique namespace related to the project |
| `platform_kubernetes_attributes[authorization_type]` | String | no | The cluster authorization type: `rbac`, `abac` or `unknown_authorization`. Defaults to `rbac`. |
+| `environment_scope` | String | no | The associated environment to the cluster. Defaults to `*` **[PREMIUM]** |
Example request:
@@ -256,6 +257,7 @@ Parameters:
| `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes |
| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
| `platform_kubernetes_attributes[namespace]` | String | no | The unique namespace related to the project |
+| `environment_scope` | String | no | The associated environment to the cluster **[PREMIUM]** |
NOTE: **Note:**
`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
diff --git a/doc/api/project_level_variables.md b/doc/api/project_level_variables.md
index 3b00f6f140e..66a749e4811 100644
--- a/doc/api/project_level_variables.md
+++ b/doc/api/project_level_variables.md
@@ -1,4 +1,4 @@
-# Project-level Variables API
+# Project-level Variables API
## List project variables
@@ -66,14 +66,15 @@ Create a new variable.
POST /projects/:id/variables
```
-| Attribute | Type | required | Description |
-|-----------------|---------|----------|-----------------------|
-| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
-| `value` | string | yes | The `value` of a variable |
-| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
-| `protected` | boolean | no | Whether the variable is protected |
-| `masked` | boolean | no | Whether the variable is masked |
+| Attribute | Type | required | Description |
+|---------------------|---------|----------|-----------------------|
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
+| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
+| `protected` | boolean | no | Whether the variable is protected |
+| `masked` | boolean | no | Whether the variable is masked |
+| `environment_scope` | string | no | The `environment_scope` of the variable **[PREMIUM]** |
```
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
@@ -83,9 +84,11 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
{
"key": "NEW_VARIABLE",
"value": "new value",
+ "protected": false,
"variable_type": "env_var",
"protected": false,
- "masked": false
+ "masked": false,
+ "environment_scope": "*"
}
```
@@ -97,14 +100,15 @@ Update a project's variable.
PUT /projects/:id/variables/:key
```
-| Attribute | Type | required | Description |
-|-----------------|---------|----------|-------------------------|
-| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `key` | string | yes | The `key` of a variable |
-| `value` | string | yes | The `value` of a variable |
-| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
-| `protected` | boolean | no | Whether the variable is protected |
-| `masked` | boolean | no | Whether the variable is masked |
+| Attribute | Type | required | Description |
+|---------------------|---------|----------|-------------------------|
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `key` | string | yes | The `key` of a variable |
+| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
+| `protected` | boolean | no | Whether the variable is protected |
+| `masked` | boolean | no | Whether the variable is masked |
+| `environment_scope` | string | no | The `environment_scope` of the variable **[PREMIUM]** |
```
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
@@ -116,7 +120,8 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
"value": "updated value",
"variable_type": "env_var",
"protected": true,
- "masked": false
+ "masked": false,
+ "environment_scope": "*"
}
```
diff --git a/doc/api/search.md b/doc/api/search.md
index c2dd72479c1..da81c8321c9 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -19,6 +19,8 @@ GET /search
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs, users.
+If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md). **[STARTER]**
+
The response depends on the requested scope.
### Scope: projects
@@ -281,6 +283,98 @@ Example response:
]
```
+### Scope: wiki_blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=wiki_blobs&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "home",
+ "data": "hello\n\nand bye\n\nend",
+ "filename": "home.md",
+ "id": null,
+ "ref": "master",
+ "startline": 5,
+ "project_id": 6
+ }
+]
+```
+
+### Scope: commits **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=commits&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "id": "4109c2d872d5fdb1ed057400d103766aaea97f98",
+ "short_id": "4109c2d8",
+ "title": "goodbye $.browser",
+ "created_at": "2013-02-18T22:02:54.000Z",
+ "parent_ids": [
+ "59d05353ab575bcc2aa958fe1782e93297de64c9"
+ ],
+ "message": "goodbye $.browser\n",
+ "author_name": "angus croll",
+ "author_email": "anguscroll@gmail.com",
+ "authored_date": "2013-02-18T22:02:54.000Z",
+ "committer_name": "angus croll",
+ "committer_email": "anguscroll@gmail.com",
+ "committed_date": "2013-02-18T22:02:54.000Z",
+ "project_id": 6
+ }
+]
+```
+
+### Scope: blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+Filters are available for this scope:
+- filename
+- path
+- extension
+
+to use a filter simply include it in your query like so: `a query filename:some_name*`.
+
+You may use wildcards (`*`) to use glob matching.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=blobs&search=installation
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "README",
+ "data": "```\n\n## Installation\n\nQuick start using the [pre-built",
+ "filename": "README.md",
+ "id": null,
+ "ref": "master",
+ "startline": 46,
+ "project_id": 6
+ }
+]
+```
+
### Scope: users
```bash
@@ -320,6 +414,8 @@ GET /groups/:id/search
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users.
+If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md). **[STARTER]**
+
The response depends on the requested scope.
### Scope: projects
@@ -520,6 +616,98 @@ Example response:
]
```
+### Scope: wiki_blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=wiki_blobs&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "home",
+ "data": "hello\n\nand bye\n\nend",
+ "filename": "home.md",
+ "id": null,
+ "ref": "master",
+ "startline": 5,
+ "project_id": 6
+ }
+]
+```
+
+### Scope: commits **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=commits&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "id": "4109c2d872d5fdb1ed057400d103766aaea97f98",
+ "short_id": "4109c2d8",
+ "title": "goodbye $.browser",
+ "created_at": "2013-02-18T22:02:54.000Z",
+ "parent_ids": [
+ "59d05353ab575bcc2aa958fe1782e93297de64c9"
+ ],
+ "message": "goodbye $.browser\n",
+ "author_name": "angus croll",
+ "author_email": "anguscroll@gmail.com",
+ "authored_date": "2013-02-18T22:02:54.000Z",
+ "committer_name": "angus croll",
+ "committer_email": "anguscroll@gmail.com",
+ "committed_date": "2013-02-18T22:02:54.000Z",
+ "project_id": 6
+ }
+]
+```
+
+### Scope: blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+Filters are available for this scope:
+- filename
+- path
+- extension
+
+to use a filter simply include it in your query like so: `a query filename:some_name*`.
+
+You may use wildcards (`*`) to use glob matching.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=blobs&search=installation
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "README",
+ "data": "```\n\n## Installation\n\nQuick start using the [pre-built",
+ "filename": "README.md",
+ "id": null,
+ "ref": "master",
+ "startline": 46,
+ "project_id": 6
+ }
+]
+```
+
### Scope: users
```bash
@@ -556,7 +744,6 @@ GET /projects/:id/search
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
-| `ref` | string | no | The name of a repository branch or tag to search on. The project's default branch is used by default. This is only applicable for scopes: commits, blobs, and wiki_blobs. |
Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users.
@@ -851,7 +1038,7 @@ Blobs searches are performed on both filenames and contents. Search results:
times in the content.
```bash
-curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation&ref=feature
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation
```
Example response:
@@ -864,7 +1051,7 @@ Example response:
"data": "```\n\n## Installation\n\nQuick start using the [pre-built",
"filename": "README.md",
"id": null,
- "ref": "feature",
+ "ref": "master",
"startline": 46,
"project_id": 6
}
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 93b82a065b3..635cce13b4e 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -161,6 +161,33 @@ See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJ
As GitLab CI/CD has evolved, certain breaking changes have
been necessary. These are:
+#### 12.0
+
+- [Use refspec to clone/fetch git
+ repository](https://gitlab.com/gitlab-org/gitlab-runner/issues/4069).
+- [Old cache
+ configuration](https://gitlab.com/gitlab-org/gitlab-runner/issues/4070).
+- [Old metrics server
+ configuration](https://gitlab.com/gitlab-org/gitlab-runner/issues/4072).
+- [Remove
+ `FF_K8S_USE_ENTRYPOINT_OVER_COMMAND`](https://gitlab.com/gitlab-org/gitlab-runner/issues/4073).
+- [Remove Linux distributions that reach
+ EOL](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1130).
+- [Update command line API for helper
+ images](https://gitlab.com/gitlab-org/gitlab-runner/issues/4013).
+- [Remove old `git clean`
+ flow](https://gitlab.com/gitlab-org/gitlab-runner/issues/4175).
+
+#### 11.0
+
+- No breaking changes.
+
+#### 10.0
+
+- No breaking changes.
+
+#### 9.0
+
- [CI variables renaming for GitLab 9.0](variables/deprecated_variables.md#gitlab-90-renamed-variables). Read about the
deprecated CI variables and what you should use for GitLab 9.0+.
- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md).
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index d703e106e76..9a5a3624c73 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -1,3 +1,7 @@
+---
+type: index, concepts, howto
+---
+
# Cache dependencies in GitLab CI/CD
GitLab CI/CD provides a caching mechanism that can be used to save time
@@ -60,7 +64,7 @@ In summary:
- Caches are disabled if not defined globally or per job (using `cache:`).
- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally.
-- Caches can be used by subsequent pipelines of that very same job (a script in
+- Caches can be used by subsequent pipelines of that same job (a script in
a stage) in which the cache was created (if not defined globally).
- Caches are stored where the Runner is installed **and** uploaded to S3 if
[distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching).
@@ -87,7 +91,7 @@ cache, when declaring `cache` in your jobs, use one or a mix of the following:
that share their cache.
- [Use sticky Runners](../runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
that will be only available to a particular project.
-- [Use a `key`](../yaml/README.md#cachekey) that fits your workflow (e.g.,
+- [Use a `key`](../yaml/README.md#cachekey) that fits your workflow (for example,
different caches on each branch). For that, you can take advantage of the
[CI/CD predefined variables](../variables/README.md#predefined-environment-variables).
@@ -420,7 +424,7 @@ mismatch and a few ideas how to fix it.
Let's explore some examples.
----
+#### Examples
Let's assume you have only one Runner assigned to your project, so the cache
will be stored in the Runner's machine by default. If two jobs, A and B,
@@ -462,8 +466,6 @@ job B:
To fix that, use different `keys` for each job.
----
-
In another case, let's assume you have more than one Runners assigned to your
project, but the distributed cache is not enabled. We want the second time the
pipeline is run, `job A` and `job B` to re-use their cache (which in this case
@@ -526,3 +528,15 @@ Behind the scenes, this works by increasing a counter in the database, and the
value of that counter is used to create the key for the cache by appending an
integer to it: `-1`, `-2`, etc. After a push, a new key is generated and the
old cache is not valid anymore.
+
+<!-- ## 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/ci/chatops/README.md b/doc/ci/chatops/README.md
index 5ecdf0f8c54..241134783da 100644
--- a/doc/ci/chatops/README.md
+++ b/doc/ci/chatops/README.md
@@ -1,3 +1,7 @@
+---
+type: index, concepts, howto
+---
+
# GitLab ChatOps
> **Notes:**
@@ -10,7 +14,10 @@ GitLab ChatOps provides a method to interact with CI/CD jobs through chat servic
## How it works
-GitLab ChatOps is built upon two existing features, [GitLab CI/CD](../README.md) and [Slack Slash Commmands](../../user/project/integrations/slack_slash_commands.md).
+GitLab ChatOps is built upon two existing features:
+
+- [GitLab CI/CD](../README.md).
+- [Slack Slash Commands](../../user/project/integrations/slack_slash_commands.md).
A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [.gitlab-ci.yml](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.md#predefined-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
@@ -22,9 +29,9 @@ After the job has finished, its output is sent back to Slack provided it has com
Since ChatOps is built upon GitLab CI/CD, the job has all the same features and functions available. There a few best practices to consider however when creating ChatOps jobs:
-* It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline.
-* If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started.
-* It is important to keep in mind that there is very limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.html#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](../variables/README.html#priority-of-environment-variables).
+- It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline.
+- If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started.
+- It is important to keep in mind that there is limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.html#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](../variables/README.html#priority-of-environment-variables).
### Controlling the ChatOps reply
@@ -59,3 +66,15 @@ You can find and download the official GitLab ChatOps icon here.
![GitLab ChatOps bot icon](img/gitlab-chatops-icon-small.png)
[Download bigger image](img/gitlab-chatops-icon.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/ci/ci_cd_for_external_repos/bitbucket_integration.md b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
index 1c8e12d229c..9126eb65f07 100644
--- a/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
@@ -1,8 +1,15 @@
+---
+type: howto
+---
+
# Using GitLab CI/CD with a Bitbucket Cloud repository **[PREMIUM]**
-GitLab CI/CD can be used with Bitbucket Cloud by creating a
-[CI/CD project](https://docs.gitlab.com/ee/user/project/ci_cd_for_external_repo.html) and connecting
-your Git repository via URL.
+GitLab CI/CD can be used with Bitbucket Cloud by:
+
+1. Creating a [CI/CD project](https://docs.gitlab.com/ee/user/project/ci_cd_for_external_repo.html).
+1. Connecting your Git repository via URL.
+
+To use GitLab CI/CD with a Bitbucket Cloud repository:
1. In GitLab create a **CI/CD for external repo**, select **Repo by URL** and
create the project.
@@ -16,13 +23,13 @@ your Git repository via URL.
with `api` scope. This will be used to authenticate requests from the web
hook that will be created in Bitbucket to notify GitLab of new commits.
-1. In Bitbucket from **Settings > Webhooks** create a new web hook to notify
+1. In Bitbucket, from **Settings > Webhooks**, create a new web hook to notify
GitLab of new commits.
The web hook URL should be set to the GitLab API to trigger pull mirroring,
using the Personal Access Token we just generated for authentication.
- ```
+ ```text
https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
```
@@ -33,27 +40,27 @@ your Git repository via URL.
After saving, test the web hook by pushing a change to your Bitbucket
repository.
-1. In Bitbucket create an **App Password** from **Bitbucket Settings > App
+1. In Bitbucket, create an **App Password** from **Bitbucket Settings > App
Passwords** to authenticate the build status script setting commit build
statuses in Bitbucket. Repository write permissions are required.
![Bitbucket Cloud webhook](img/bitbucket_app_password.png)
-1. In GitLab from **Settings > CI/CD > Environment variables** add variables to allow
- communication with Bitbucket via the Bitbucket API.
+1. In GitLab, from **Settings > CI/CD > Environment variables**, add variables to allow
+ communication with Bitbucket via the Bitbucket API:
- `BITBUCKET_ACCESS_TOKEN`: the Bitbucket app password created above
+ `BITBUCKET_ACCESS_TOKEN`: the Bitbucket app password created above.
- `BITBUCKET_USERNAME`: the username of the Bitbucket account
+ `BITBUCKET_USERNAME`: the username of the Bitbucket account.
- `BITBUCKET_NAMESPACE`: set this if your GitLab and Bitbucket namespaces differ
+ `BITBUCKET_NAMESPACE`: set this if your GitLab and Bitbucket namespaces differ.
- `BITBUCKET_REPOSITORY`: set this if your GitLab and Bitbucket project names differ
+ `BITBUCKET_REPOSITORY`: set this if your GitLab and Bitbucket project names differ.
-1. In Bitbucket add a script to push the pipeline status to Bitbucket.
+1. In Bitbucket, add a script to push the pipeline status to Bitbucket.
> Note: changes made in GitLab will be overwritten by any changes made
- upstream in Bitbucket.
+ > upstream in Bitbucket.
Create a file `build_status` and insert the script below and run
`chmod +x build_status` in your terminal to make the script executable.
@@ -111,7 +118,7 @@ your Git repository via URL.
1. Still in Bitbucket, create a `.gitlab-ci.yml` file to use the script to push
pipeline success and failures to Bitbucket.
- ```
+ ```yaml
stages:
- test
- ci_status
@@ -145,3 +152,15 @@ GitLab is now configured to mirror changes from Bitbucket, run CI/CD pipelines
configured in `.gitlab-ci.yml` and push the status to Bitbucket.
[pull-mirroring]: ../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter
+
+<!-- ## 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/ci/ci_cd_for_external_repos/github_integration.md b/doc/ci/ci_cd_for_external_repos/github_integration.md
index 0e2acf957e0..612dcc93bc1 100644
--- a/doc/ci/ci_cd_for_external_repos/github_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/github_integration.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Using GitLab CI/CD with a GitHub repository **[PREMIUM]**
GitLab CI/CD can be used with **GitHub.com** and **GitHub Enterprise** by
@@ -15,7 +19,7 @@ administrator:
NOTE: **Note:**
Due to a 10-token limitation on the [GitHub OAuth Implementation](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#creating-multiple-tokens-for-oauth-apps),
-if you import more than 10 times, your oldest imported project's token will be
+if you import more than 10 times, your oldest imported project's token will be
revoked. See issue [#9147](https://gitlab.com/gitlab-org/gitlab-ee/issues/9147)
for more information.
@@ -31,23 +35,27 @@ for more information.
1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
-GitLab will import the project, enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter), enable
-[GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html), and create a web hook
-on GitHub to notify GitLab of new commits.
+GitLab will:
+
+1. Import the project.
+1. Enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter).
+1. Enable [GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html).
+1. Create a web hook on GitHub to notify GitLab of new commits.
## Connect with Personal Access Token
-NOTE: **Note:** Personal access tokens can only be used to connect GitHub.com
+NOTE: **Note:**
+Personal access tokens can only be used to connect GitHub.com
repositories to GitLab.
If you are not using the [GitHub integration](../../integration/github.md), you can
still perform a one-off authorization with GitHub to grant GitLab access your
repositories:
-1. Open https://github.com/settings/tokens/new to create a **Personal Access
+1. Open <https://github.com/settings/tokens/new> to create a **Personal Access
Token**. This token with be used to access your repository and push commit
statuses to GitHub.
-
+
The `repo` and `admin:repo_hook` should be enable to allow GitLab access to
your project, update commit statuses, and create a web hook to notify
GitLab of new commits.
@@ -62,20 +70,23 @@ repositories:
1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
-GitLab will import the project, enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter), enable
-[GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html), and create a web hook
-on GitHub to notify GitLab of new commits.
+GitLab will:
+
+1. Import the project.
+1. Enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter).
+1. Enable [GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html).
+1. Create a web hook on GitHub to notify GitLab of new commits.
## Connect manually
If the [GitHub integration](../../integration/github.md) is not enabled, or is enabled
for a different GitHub instance, you GitLab CI/CD can be manually enabled for
-your repository.
+your repository:
-1. In GitHub open https://github.com/settings/tokens/new create a **Personal
+1. In GitHub open <https://github.com/settings/tokens/new> create a **Personal
Access Token.** GitLab will use this token to access your repository and
push commit statuses.
-
+
Enter a **Token description** and update the scope to allow:
`repo` so that GitLab can access your project and update commit statuses
@@ -109,3 +120,15 @@ your repository.
![Create web hook](img/github_push_webhook.png)
1. In GitHub add a `.gitlab-ci.yml` to configure GitLab CI/CD.
+
+<!-- ## 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/ci/ci_cd_for_external_repos/index.md b/doc/ci/ci_cd_for_external_repos/index.md
index 450ffe512dc..5de88412121 100644
--- a/doc/ci/ci_cd_for_external_repos/index.md
+++ b/doc/ci/ci_cd_for_external_repos/index.md
@@ -1,3 +1,7 @@
+---
+type: index, howto
+---
+
# GitLab CI/CD for external repositories **[PREMIUM]**
NOTE: **Note:**
@@ -6,22 +10,26 @@ GitLab.com users until September 22nd, 2019.
>[Introduced][ee-4642] in [GitLab Premium][eep] 10.6.
-GitLab CI/CD can be used with GitHub or any other Git server.
+GitLab CI/CD can be used with:
+
+- [GitHub](github_integration.md).
+- [Bitbucket Cloud](bitbucket_integration.md).
+- Any other Git server.
+
Instead of moving your entire project to GitLab, you can connect your
external repository to get the benefits of GitLab CI/CD.
-- [GitHub](github_integration.md)
-- [Bitbucket Cloud](bitbucket_integration.md)
-
Connecting an external repository will set up [repository mirroring][mirroring]
and create a lightweight project where issues, merge requests, wiki, and
snippets disabled. These features
[can be re-enabled later][settings].
-1. From your GitLab dashboard click **New project**
-1. Switch to the **CI/CD for external repo** tab
-1. Choose **GitHub** or **Repo by URL**
-1. The next steps are similar to the [import flow](../../user/project/import/index.md)
+To connect to an external repository:
+
+1. From your GitLab dashboard, click **New project**.
+1. Switch to the **CI/CD for external repo** tab.
+1. Choose **GitHub** or **Repo by URL**.
+1. The next steps are similar to the [import flow](../../user/project/import/index.md).
![CI/CD for external repository project creation](img/ci_cd_for_external_repo.png)
diff --git a/doc/ci/docker/README.md b/doc/ci/docker/README.md
index 446f5b54f0c..f76471b50f2 100644
--- a/doc/ci/docker/README.md
+++ b/doc/ci/docker/README.md
@@ -1,9 +1,13 @@
---
comments: false
+type: index
---
# Docker integration
+GitLab CI/CD can be combined with [Docker](https://www.docker.com) to enable
+integration between the two.
+
The following documentation is available for using GitLab CI/CD with Docker:
- [Using Docker images](using_docker_images.md).
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 6920ce17b46..d8068bbb7f0 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Building Docker images with GitLab CI/CD
GitLab CI/CD allows you to use Docker Engine to build and test docker-based projects.
@@ -5,10 +9,10 @@ GitLab CI/CD allows you to use Docker Engine to build and test docker-based proj
One of the new trends in Continuous Integration/Deployment is to:
-1. Create an application image
-1. Run tests against the created image
-1. Push image to a remote registry
-1. Deploy to a server from the pushed image
+1. Create an application image.
+1. Run tests against the created image.
+1. Push image to a remote registry.
+1. Deploy to a server from the pushed image.
It's also useful when your application already has the `Dockerfile` that can be
used to create and test an image:
@@ -113,7 +117,7 @@ In order to do that, follow the steps:
The above command will create a `config.toml` entry similar to this:
- ```
+ ```toml
[[runners]]
url = "https://gitlab.com/"
token = TOKEN
@@ -227,7 +231,7 @@ In order to do that, follow the steps:
The above command will create a `config.toml` entry similar to this:
- ```
+ ```toml
[[runners]]
url = "https://gitlab.com/"
token = REGISTRATION_TOKEN
@@ -270,9 +274,9 @@ aware of the following implications:
create containers with specific names, they may conflict with each other.
- Sharing files and directories from the source repo into containers may not
work as expected since volume mounting is done in the context of the host
- machine, not the build container, e.g.:
+ machine, not the build container. For example:
- ```
+ ```sh
docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
```
@@ -337,7 +341,7 @@ NOTE: **Note:**
The shared Runners on GitLab.com use the `overlay2` driver by default.
By default, when using `docker:dind`, Docker uses the `vfs` storage driver which
-copies the filesystem on every run. This is a very disk-intensive operation
+copies the filesystem on every run. This is a disk-intensive operation
which can be avoided if a different driver is used, for example `overlay2`.
### Requirements
@@ -345,13 +349,13 @@ which can be avoided if a different driver is used, for example `overlay2`.
1. Make sure a recent kernel is used, preferably `>= 4.2`.
1. Check whether the `overlay` module is loaded:
- ```
+ ```sh
sudo lsmod | grep overlay
```
If you see no result, then it isn't loaded. To load it use:
- ```
+ ```sh
sudo modprobe overlay
```
@@ -359,7 +363,7 @@ which can be avoided if a different driver is used, for example `overlay2`.
On Ubuntu systems, this is done by editing `/etc/modules`. Just add the
following line into it:
- ```
+ ```text
overlay
```
@@ -367,7 +371,7 @@ which can be avoided if a different driver is used, for example `overlay2`.
You can enable the driver for each project individually by editing the project's `.gitlab-ci.yml`:
-```
+```yaml
variables:
DOCKER_DRIVER: overlay2
```
@@ -571,3 +575,15 @@ deploy:
[docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
[2fa]: ../../user/profile/account/two_factor_authentication.md
[pat]: ../../user/profile/personal_access_tokens.md
+
+<!-- ## 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/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 13c26bc5f47..d09efce7cc1 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Using Docker images
GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
@@ -45,10 +49,10 @@ The `image` keyword is the name of the Docker image the Docker executor
will run to perform the CI tasks.
By default, the executor will only pull images from [Docker Hub][hub],
-but this can be configured in the `gitlab-runner/config.toml` by setting
+however this can be configured in the `gitlab-runner/config.toml` by setting
the [Docker pull policy][] to allow using local images.
-For more information about images and Docker Hub please read
+For more information about images and Docker Hub, please read
the [Docker Fundamentals][] documentation.
## What is a service
@@ -95,8 +99,8 @@ required for the CI/CD job to proceed and is accessed by network.
To make sure this works, the Runner:
-1. checks which ports are exposed from the container by default
-1. starts a special container that waits for these ports to be accessible
+1. Checks which ports are exposed from the container by default.
+1. Starts a special container that waits for these ports to be accessible.
When the second stage of the check fails, either because there is no opened port in the
service, or the service was not started properly before the timeout and the port is not
@@ -106,7 +110,7 @@ In most cases it will affect the job, but there may be situations when the job
will still succeed even if that warning was printed. For example:
- The service was started a little after the warning was raised, and the job is
- not using the linked service from the very beginning. In that case, when the
+ not using the linked service from the beginning. In that case, when the
job needed to access the service, it may have been already there waiting for
connections.
- The service container is not providing any networking service, but it's doing
@@ -143,9 +147,9 @@ job:
If you need to have `php`, `node` and `go` available for your script, you should
either:
-- choose an existing Docker image that contains all required tools, or
-- create your own Docker image, which will have all the required tools included
- and use that in your job
+- Choose an existing Docker image that contains all required tools.
+- Create your own Docker image, which will have all the required tools included
+ and use that in your job.
### Accessing the services
@@ -167,18 +171,18 @@ access to it from your build container under two hostnames to choose from:
- `tutum-wordpress`
- `tutum__wordpress`
->**Note:**
+NOTE: **Note:**
Hostnames with underscores are not RFC valid and may cause problems in 3rd party
applications.
The default aliases for the service's hostname are created from its image name
following these rules:
-- Everything after the colon (`:`) is stripped
+- Everything after the colon (`:`) is stripped.
- Slash (`/`) is replaced with double underscores (`__`) and the primary alias
- is created
+ is created.
- Slash (`/`) is replaced with a single dash (`-`) and the secondary alias is
- created (requires GitLab Runner v1.1.0 or higher)
+ created (requires GitLab Runner v1.1.0 or higher).
To override the default behavior, you can
[specify a service alias](#available-settings-for-services).
@@ -333,7 +337,7 @@ services:
```
The Runner will still start two containers using the `mysql:latest` image,
-but now each of them will also be accessible with the alias configured
+however now each of them will also be accessible with the alias configured
in `.gitlab-ci.yml` file.
### Setting a command for the service
@@ -408,8 +412,6 @@ you should check which one your Runner is using. Specifically:
The syntax of `image:entrypoint` is similar to [Dockerfile's `ENTRYPOINT`][entrypoint].
-----
-
Let's assume you have a `super/sql:experimental` image with some SQL database
inside it and you would like to use it as a base image for your job because you
want to execute some tests with this database binary. Let's also assume that
@@ -443,7 +445,7 @@ image:
Look for the `[runners.docker]` section:
-```
+```toml
[runners.docker]
image = "ruby:2.1"
services = ["mysql:latest", "postgres:latest"]
@@ -469,11 +471,11 @@ image which is private and requires you to login into a private container regist
Let's also assume that these are the login credentials:
-| Key | Value |
-|----------|---------------------------|
-| registry | registry.example.com:5000 |
-| username | my_username |
-| password | my_password |
+| Key | Value |
+|:---------|:----------------------------|
+| registry | `registry.example.com:5000` |
+| username | `my_username` |
+| password | `my_password` |
To configure access for `registry.example.com:5000`, follow these steps:
@@ -534,7 +536,8 @@ To configure access for `registry.example.com:5000`, follow these steps:
You can add configuration for as many registries as you want, adding more
registries to the `"auths"` hash as described above.
-NOTE: **Note:** The full `hostname:port` combination is required everywhere
+NOTE: **Note:**
+The full `hostname:port` combination is required everywhere
for the Runner to match the `DOCKER_AUTH_CONFIG`. For example, if
`registry.example.com:5000/namespace/image:tag` is specified in `.gitlab-ci.yml`,
then the `DOCKER_AUTH_CONFIG` must also specify `registry.example.com:5000`.
@@ -551,8 +554,9 @@ service containers.
For all possible configuration variables check the documentation of each image
provided in their corresponding Docker hub page.
-*Note: All variables will be passed to all services containers. It's not
-designed to distinguish which variable should go where.*
+NOTE: **Note:**
+All variables will be passed to all services containers. It's not
+designed to distinguish which variable should go where.
### PostgreSQL service example
@@ -582,8 +586,9 @@ time.
## How to debug a job locally
-*Note: The following commands are run without root privileges. You should be
-able to run Docker with your regular user account.*
+NOTE: **Note:**
+The following commands are run without root privileges. You should be
+able to run Docker with your regular user account.
First start with creating a file named `build_script`:
@@ -602,7 +607,7 @@ is specific to your project.
Then create some service containers:
-```
+```sh
docker run -d --name service-mysql mysql:latest
docker run -d --name service-postgres postgres:latest
```
@@ -614,7 +619,7 @@ respectively. They will both run in the background (`-d`).
Finally, create a build container by executing the `build_script` file we
created earlier:
-```
+```sh
docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
```
@@ -626,7 +631,7 @@ piped using STDIN to the bash interpreter which in turn executes the
When you finish testing and no longer need the containers, you can remove them
with:
-```
+```sh
docker rm -f -v build service-mysql service-postgres
```
diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md
index f354cdb398e..50f1ac3d54a 100644
--- a/doc/ci/docker/using_kaniko.md
+++ b/doc/ci/docker/using_kaniko.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Building images with kaniko and GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45512) in GitLab 11.2.
@@ -9,17 +13,18 @@ container images from a Dockerfile, inside a container or Kubernetes cluster.
kaniko solves two problems with using the
[docker-in-docker build](using_docker_build.md#use-docker-in-docker-executor) method:
-1. Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
- in order to function, which is a significant security concern.
-1. Docker-in-docker generally incurs a performance penalty and can be quite slow.
+- Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
+ in order to function, which is a significant security concern.
+- Docker-in-docker generally incurs a performance penalty and can be quite slow.
## Requirements
In order to utilize kaniko with GitLab, a [GitLab Runner](https://docs.gitlab.com/runner/)
-using either the [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html),
-[Docker](https://docs.gitlab.com/runner/executors/docker.html), or
-[Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html)
-executors is required.
+using one of the following executors is required:
+
+- [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html).
+- [Docker](https://docs.gitlab.com/runner/executors/docker.html).
+- [Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html).
## Building a Docker image with kaniko
@@ -34,14 +39,17 @@ few important details:
- A Docker `config.json` file needs to be created with the authentication
information for the desired container registry.
----
+In the following example, kaniko is used to:
+
+1. Build a Docker image.
+1. Then push it to [GitLab Container Registry](../../user/project/container_registry.md).
-In the following example, kaniko is used to build a Docker image and then push
-it to [GitLab Container Registry](../../user/project/container_registry.md).
The job will run only when a tag is pushed. A `config.json` file is created under
`/kaniko/.docker` with the needed GitLab Container Registry credentials taken from the
[environment variables](../variables/README.md#predefined-environment-variables)
-GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the
+GitLab CI/CD provides.
+
+In the last step, kaniko uses the `Dockerfile` under the
root directory of the project, builds the Docker image and pushes it to the
project's Container Registry while tagging it with the Git tag:
@@ -80,3 +88,15 @@ store:
...
-----END CERTIFICATE-----" >> /kaniko/ssl/certs/ca-certificates.crt
```
+
+<!-- ## 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/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md
index 9934d543991..56200142055 100644
--- a/doc/ci/enable_or_disable_ci.md
+++ b/doc/ci/enable_or_disable_ci.md
@@ -1,29 +1,32 @@
---
-type: reference
+type: howto
---
# How to enable or disable GitLab CI/CD
-To effectively use GitLab CI/CD, you need a valid [`.gitlab-ci.yml`](yaml/README.md)
-file present at the root directory of your project and a
-[runner](runners/README.md) properly set up. You can read our
-[quick start guide](quick_start/README.md) to get you started.
+To effectively use GitLab CI/CD, you need:
+
+- A valid [`.gitlab-ci.yml`](yaml/README.md) file present at the root directory
+ of your project.
+- A [runner](runners/README.md) properly set up.
+
+You can read our [quick start guide](quick_start/README.md) to get you started.
If you are using an external CI/CD server like Jenkins or Drone CI, it is advised
to disable GitLab CI/CD in order to not have any conflicts with the commits status
API.
----
-
GitLab CI/CD is exposed via the `/pipelines` and `/jobs` pages of a project.
Disabling GitLab CI/CD in a project does not delete any previous jobs.
In fact, the `/pipelines` and `/jobs` pages can still be accessed, although
it's hidden from the left sidebar menu.
-GitLab CI/CD is enabled by default on new installations and can be disabled either
-individually under each project's settings, or site-wide by modifying the
-settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations
-respectively.
+GitLab CI/CD is enabled by default on new installations and can be disabled
+either:
+
+- Individually under each project's settings.
+- Site-wide by modifying the settings in `gitlab.yml` and `gitlab.rb` for source
+ and Omnibus installations respectively.
## Per-project user setting
@@ -40,10 +43,10 @@ and `gitlab.rb` for source and Omnibus installations respectively.
Two things to note:
-1. Disabling GitLab CI/CD, will affect only newly-created projects. Projects that
- had it enabled prior to this modification, will work as before.
-1. Even if you disable GitLab CI/CD, users will still be able to enable it in the
- project's settings.
+- Disabling GitLab CI/CD, will affect only newly-created projects. Projects that
+ had it enabled prior to this modification, will work as before.
+- Even if you disable GitLab CI/CD, users will still be able to enable it in the
+ project's settings.
For installations from source, open `gitlab.yml` with your editor and set
`builds` to `false`:
@@ -58,12 +61,32 @@ default_projects_features:
builds: false
```
-Save the file and restart GitLab: `sudo service gitlab restart`.
+Save the file and restart GitLab:
+
+```sh
+sudo service gitlab restart
+```
For Omnibus installations, edit `/etc/gitlab/gitlab.rb` and add the line:
-```
+```ruby
gitlab_rails['gitlab_default_projects_features_builds'] = false
```
-Save the file and reconfigure GitLab: `sudo gitlab-ctl reconfigure`.
+Save the file and reconfigure GitLab:
+
+```sh
+sudo gitlab-ctl reconfigure
+```
+
+<!-- ## 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/ci/environments.md b/doc/ci/environments.md
index 9f930833093..f2661c4bafd 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -92,7 +92,7 @@ deploy_staging:
- master
```
-We have defined 3 [stages](yaml/README.md#stages):
+We have defined three [stages](yaml/README.md#stages):
- `test`
- `build`
@@ -135,7 +135,7 @@ In summary, with the above `.gitlab-ci.yml` we have achieved the following:
> the name given in `.gitlab-ci.yml` (with any variables expanded), while the
> second is a "cleaned-up" version of the name, suitable for use in URLs, DNS,
> etc.
-
+>
> Starting with GitLab 9.3, the environment URL is exposed to the Runner via
> `$CI_ENVIRONMENT_URL`. The URL is expanded from `.gitlab-ci.yml`, or if
> the URL was not defined there, the external URL from the environment is used.
diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md
index ab5c0e2dbad..b72ebe838b8 100644
--- a/doc/ci/environments/protected_environments.md
+++ b/doc/ci/environments/protected_environments.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Protected Environments **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6303) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
@@ -47,3 +51,15 @@ Maintainers can:
- Update existing protected environments at any time by changing the access in the
**Allowed to Deploy** dropdown menu.
- Unprotect a protected environment by clicking the **Unprotect** button for that environment.
+
+<!-- ## 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/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
index 1091129a9dc..50c8d82602b 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -26,6 +26,10 @@ and when hovering or tapping (on touchscreen devices) they will expand and be sh
Multi-project pipelines are useful for larger products that require cross-project inter-dependencies, such as those
adopting a [microservices architecture](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/).
+For a demonstration of how cross-functional development teams can use cross-pipeline
+triggering to trigger multiple pipelines for different microservices projects, see
+[Cross-project Pipeline Triggering and Visualization](https://about.gitlab.com/handbook/marketing/product-marketing/demo/#cross-project-pipeline-triggering-and-visualization-may-2019---1110).
+
## Use cases
Let's assume you deploy your web app from different projects in GitLab:
@@ -167,10 +171,6 @@ In this scenario, the `UPSTREAM_BRANCH` variable with a value related to the
upstream pipeline will be passed to the `downstream-job` job, and will be available
within the context of all downstream builds.
-### Demos
-
-[A click-through demo of cross-project pipeline is available](https://about.gitlab.com/handbook/marketing/product-marketing/demo/#cross-project-pipeline-triggering-and-visualization-may-2019---1110), demonstrates how cross-functional dev teams use cross-pipeline triggering to trigger multiple pipelines for different microservices projects.
-
### Limitations
Because bridge jobs are a little different to regular jobs, it is not
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 8b0634ed268..1ad3d516df9 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -270,9 +270,6 @@ Clicking on an individual job will show you its job trace, and allow you to:
- Retry the job.
- Erase the job trace.
-NOTE: **Note:**
-To prevent jobs from being bypassed or run out of order, canceled jobs can only be retried when the whole pipeline they belong to is retried.
-
### Seeing the failure reason for jobs
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782) in GitLab 10.7.
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index c851931e85c..df455857dee 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -92,7 +92,7 @@ This means that the value of the variable will be hidden in job logs,
though it must match certain requirements to do so:
- The value must be in a single line.
-- The value must contain only letters, numbers, or underscores.
+- The value must only consist of characters from the Base64 alphabet, defined in [RFC4648](https://tools.ietf.org/html/rfc4648).
- The value must be at least 8 characters long.
- The value must not use variables.
@@ -479,6 +479,7 @@ Below you can find supported syntax reference:
1. Equality matching using a string
> Example: `$VARIABLE == "some value"`
+
> Example: `$VARIABLE != "some value"` _(added in 11.11)_
You can use equality operator `==` or `!=` to compare a variable content to a
@@ -489,6 +490,7 @@ Below you can find supported syntax reference:
1. Checking for an undefined value
> Example: `$VARIABLE == null`
+
> Example: `$VARIABLE != null` _(added in 11.11)_
It sometimes happens that you want to check whether a variable is defined
@@ -499,6 +501,7 @@ Below you can find supported syntax reference:
1. Checking for an empty variable
> Example: `$VARIABLE == ""`
+
> Example: `$VARIABLE != ""` _(added in 11.11)_
If you want to check whether a variable is defined, but is empty, you can
@@ -508,6 +511,7 @@ Below you can find supported syntax reference:
1. Comparing two variables
> Example: `$VARIABLE_1 == $VARIABLE_2`
+
> Example: `$VARIABLE_1 != $VARIABLE_2` _(added in 11.11)_
It is possible to compare two variables. This is going to compare values
@@ -527,6 +531,7 @@ Below you can find supported syntax reference:
1. Pattern matching _(added in 11.0)_
> Example: `$VARIABLE =~ /^content.*/`
+
> Example: `$VARIABLE_1 !~ /^content.*/` _(added in 11.11)_
It is possible perform pattern matching against a variable and regular
@@ -536,6 +541,19 @@ Below you can find supported syntax reference:
Pattern matching is case-sensitive by default. Use `i` flag modifier, like
`/pattern/i` to make a pattern case-insensitive.
+1. Conjunction / Disjunction
+
+ > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"`
+
+ > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+
+ > Example: `$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+
+ It is possible to join multiple conditions using `&&` or `||`. Any of the otherwise
+ supported syntax may be used in a conjunctive or disjunctive statement.
+ Precedence of operators follows standard Ruby 2.5 operation
+ [precedence](https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html).
+
## Debug tracing
> Introduced in GitLab Runner 1.7.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 716363a8d6d..fa4b0378f61 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1189,9 +1189,9 @@ skip the download step.
> - Job artifacts are only collected for successful jobs by default.
`artifacts` is used to specify a list of files and directories which should be
-attached to the job after success.
+attached to the job when it [succeeds, fails, or always](#artifactswhen).
-The artifacts will be sent to GitLab after the job finishes successfully and will
+The artifacts will be sent to GitLab after the job finishes and will
be available for download in the GitLab UI.
[Read more about artifacts](../../user/project/pipelines/job_artifacts.md).
diff --git a/doc/development/README.md b/doc/development/README.md
index 624665a42d1..d2f09fc01de 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -20,6 +20,7 @@ description: 'Learn how to contribute to GitLab.'
- [Automatic CE->EE merge](automatic_ce_ee_merge.md)
- [Guidelines for implementing Enterprise Edition features](ee_features.md)
- [Security process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer)
+- [Requesting access to Chatops on GitLab.com](chatops_on_gitlabcom.md#requesting-access) (for GitLabbers)
## UX and frontend guides
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index a0e4020da09..650325121b2 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -676,7 +676,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha
[runner-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/runner.md
[database-migrations-omnibus]: https://docs.gitlab.com/omnibus/settings/database.html#disabling-automatic-database-migration
[database-migrations-charts]: https://docs.gitlab.com/charts/charts/gitlab/migrations/
-[database-migrations-source]: ../update/upgrading_from_source.md#13-install-libs-migrations-etc
+[database-migrations-source]: ../update/upgrading_from_source.md#14-install-libs-migrations-etc
[certificate-management-omnibus]: https://docs.gitlab.com/omnibus/settings/ssl.html
[certificate-management-charts]: https://docs.gitlab.com/charts/installation/tls.html
[certificate-management-source]: ../install/installation.md#using-https
diff --git a/doc/development/chatops_on_gitlabcom.md b/doc/development/chatops_on_gitlabcom.md
new file mode 100644
index 00000000000..c63ec53414c
--- /dev/null
+++ b/doc/development/chatops_on_gitlabcom.md
@@ -0,0 +1,21 @@
+# Chatops on GitLab.com
+
+Chatops on GitLab.com allows GitLabbers to run various automation tasks on GitLab.com using Slack.
+
+## Requesting access
+
+GitLabbers may need access to Chatops on GitLab.com for administration tasks such as:
+
+- Configuring feature flags on staging.
+- Running `EXPLAIN` queries against the GitLab.com production replica.
+
+To request access to Chatops on GitLab.com:
+
+1. Log into <https://ops.gitlab.net/users/sign_in> using the same username as for GitLab.com.
+1. Ask [anyone in the `chatops` project](https://gitlab.com/gitlab-com/chatops/project_members) to add you by running `/chatops run member add <username> gitlab-com/chatops --ops`.
+
+## See also
+
+ - [Chatops Usage](https://docs.gitlab.com/ee/ci/chatops/README.html)
+ - [Understanding EXPLAIN plans](understanding_explain_plans.md)
+ - [Feature Groups](feature_flags.md#feature-groups)
diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md
index 77f064a21a9..e4225f2bc39 100644
--- a/doc/development/fe_guide/frontend_faq.md
+++ b/doc/development/fe_guide/frontend_faq.md
@@ -25,3 +25,17 @@ document.body.dataset.page
```
Find here the [source code setting the attribute](https://gitlab.com/gitlab-org/gitlab-ce/blob/cc5095edfce2b4d4083a4fb1cdc7c0a1898b9921/app/views/layouts/application.html.haml#L4).
+
+### `modal_copy_button` vs `clipboard_button`
+
+The `clipboard_button` uses the `copy_to_clipboard.js` behaviour, which is
+initialized on page load, so if there are vue-based clipboard buttons that
+don't exist at page load (such as ones in a `GlModal`), they do not have the
+click handlers associated with the clipboard package.
+
+`modal_copy_button` was added that manages an instance of the
+[`clipboard` plugin](https://www.npmjs.com/package/clipboard) specific to
+the instance of that component, which means that clipboard events are
+bound on mounting and destroyed when the button is, mitigating the above
+issue. It also has bindings to a particular container or modal ID
+available, to work with the focus trap created by our GlModal.
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index c871015aaf6..13f0c5cc33e 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -20,7 +20,7 @@ dynamic (querying the DB etc.).
Once defined in `lib/feature.rb`, you will be able to activate a
feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
-For GitLab.com, team members have access to feature flags through chatops. Only
+For GitLab.com, [team members have access to feature flags through Chatops](chatops_on_gitlabcom.md). Only
percentage gates are supported at this time. Setting a feature to be used 50% of
the time, you should execute `/chatops run feature set my_feature_flag 50`.
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 8f23ad4732a..9fb8ea542d9 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -174,7 +174,7 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
# => When size == 2: 'There are 2 mice.'
```
- Avoid using `%d` or count variables in sigular strings. This allows more natural translation in some languages.
+ Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
- In JavaScript:
@@ -332,7 +332,7 @@ Errors in `locale/zh_HK/gitlab.po`:
Syntax error in msgstr
Syntax error in message_line
There should be only whitespace until the end of line after the double quote character of a message text.
- Parseing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
+ Parsing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
SimplePoParser filtered backtrace: SimplePoParser::ParserError
Errors in `locale/zh_TW/gitlab.po`:
1 pipeline
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index fb5cfb6c157..35c5b155594 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -130,7 +130,7 @@ are very appreciative of the work done by translators and proofreaders!
your previous translations by [GitLab team members](https://about.gitlab.com/team/)
or [Core team members](https://about.gitlab.com/core-team/) who are fluent in
the language or current proofreaders.
- - When a request is made for the first proofreader for a lanuguage and there are no [GitLab team members](https://about.gitlab.com/team/)
+ - When a request is made for the first proofreader for a language and there are no [GitLab team members](https://about.gitlab.com/team/)
or [Core team members](https://about.gitlab.com/core-team/) who speak the language, we will request links to previous translation work in other communities or projects.
diff --git a/doc/development/testing_guide/end_to_end/dynamic_element_validation.md b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
new file mode 100644
index 00000000000..f7b3ca8bc89
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
@@ -0,0 +1,113 @@
+# Dynamic Element Validation
+
+We devised a solution to solve common test automation problems such as the dreaded `NoSuchElementException`.
+
+Other problems that dynamic element validations solve are...
+
+- When we perform an action with the mouse, we expect something to occur.
+- When our test is navigating to (or from) a page, we ensure that we are on the page we expect before
+test continuation.
+
+## How it works
+
+We interpret user actions on the page to have some sort of effect. These actions are
+
+- [Navigation](#navigation)
+- [Clicks](#clicks)
+
+### Navigation
+
+When a page is navigated to, there are elements that will always appear on the page unconditionally.
+
+Dynamic element validation is instituted when using
+
+```ruby
+Runtime::Browser.visit(:gitlab, Some::Page)
+```
+
+### Clicks
+
+When we perform a click within our tests, we expect something to occur. That something could be a component to now
+appear on the webpage, or the test to navigate away from the page entirely.
+
+Dynamic element validation is instituted when using
+
+```ruby
+click_element :my_element, Some::Page
+```
+
+### Required Elements
+
+#### Definition
+
+First it is important to define what a "required element" is.
+
+Simply put, a required element is a visible HTML element that appears on a UI component without any user input.
+
+"Visible" can be defined as
+
+- Not having any CSS preventing its display. E.g.: `display: none` or `width: 0px; height: 0px;`
+- Being able to be interacted with by the user
+
+"UI component" can be defined as
+
+- Anything the user sees
+- A button, a text field
+- A layer that sits atop the page
+
+#### Application
+
+Requiring elements is very easy. By adding `required: true` as a parameter to an `element`, you've now made it
+a requirement that the element appear on the page upon navigation.
+
+## Examples
+
+Given ...
+
+```ruby
+class MyPage < Page::Base
+ view 'app/views/view.html.haml' do
+ element :my_element, required: true
+ element :another_element, required: true
+ element :conditional_element
+ end
+
+ def open_layer
+ click_element :my_element, Layer::MyLayer
+ end
+end
+
+class Layer < Page::Component
+ view 'app/views/mylayer/layer.html.haml' do
+ element :message_content, required: true
+ end
+end
+```
+
+### Navigating
+
+Given the [source](#examples) ...
+
+```ruby
+Runtime::Browser.visit(:gitlab, Page::MyPage)
+
+execute_stuff
+```
+
+will invoke GitLab QA to scan `MyPage` for `my_element` and `another_element` to be on the page before continuing to
+`execute_stuff`
+
+### Clicking
+
+Given the [source](#examples) ...
+
+```ruby
+def open_layer
+ click_element :my_element, Layer::MyLayer
+end
+```
+
+will invoke GitLab QA to ensure that `message_content` appears on
+the Layer upon clicking `my_element`.
+
+This will imply that the Layer is indeed rendered before we continue our test.
diff --git a/doc/development/testing_guide/end_to_end/page_objects.md b/doc/development/testing_guide/end_to_end/page_objects.md
index d0de33892c4..73e1fd862c1 100644
--- a/doc/development/testing_guide/end_to_end/page_objects.md
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
@@ -82,15 +82,17 @@ module Page
end
# ...
+ end
end
end
```
-The `view` DSL method declares the filename of the view where an
-`element` is implemented.
+### Defining Elements
+
+The `view` DSL method will correspond to the rails View, partial, or vue component that renders the elements.
The `element` DSL method in turn declares an element for which a corresponding
-`qa-element-name-dasherized` CSS class need to be added to the view file.
+`qa-element-name-dasherized` CSS class will need to be added to the view file.
You can also define a value (String or Regexp) to match to the actual view
code but **this is deprecated** in favor of the above method for two reasons:
@@ -115,6 +117,37 @@ view 'app/views/my/view.html.haml' do
end
```
+### Adding Elements to a View
+
+Given the following elements...
+
+```ruby
+view 'app/views/my/view.html.haml' do
+ element :login_field
+ element :password_field
+ element :sign_in_button
+end
+```
+
+To add these elements to the view, you must change the rails View, partial, or vue component by adding a `qa-element-descriptor` class
+for each element defined.
+
+In our case, `qa-login-field`, `qa-password-field` and `qa-sign-in-button`
+
+**app/views/my/view.html.haml**
+
+```haml
+= f.text_field :login, class: "form-control top qa-login-field", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
+= f.password_field :password, class: "form-control bottom qa-password-field", required: true, title: "This field is required."
+= f.submit "Sign in", class: "btn btn-success qa-sign-in-button"
+```
+
+Things to note:
+
+- The CSS class must be `kebab-cased` (separated with hyphens "`-`")
+- If the element appears on the page unconditionally, add `required: true` to the element. See
+[Dynamic element validation](dynamic_element_validation.md)
+
## Running the test locally
During development, you can run the `qa:selectors` test by running
diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index 2ef8b3148e4..bfbb7be70e3 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -654,6 +654,7 @@ and related tools such as:
- <https://explain.depesz.com/>
- <http://tatiyants.com/postgres-query-plan-visualization/>
+
## Producing query plans
There are a few ways to get the output of a query plan. Of course you
@@ -683,9 +684,9 @@ Execution time: 0.113 ms
### Chatops
-GitLab employees can also use our chatops solution, available in Slack using the
-`/chatops` slash command. You can use chatops to get a query plan by running the
-following:
+[GitLab employees can also use our chatops solution, available in Slack using the
+`/chatops` slash command](chatops_on_gitlabcom.md).
+You can use chatops to get a query plan by running the following:
```
/chatops run explain SELECT COUNT(*) FROM projects WHERE visibility_level IN (0, 20)
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 0a037b3876b..57a5a42fbed 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -192,9 +192,6 @@ Performing asynchronous indexing, as this will describe, will generate a lot of
Make sure to prepare for this task by either [Horizontally Scaling](../administration/high_availability/README.md#basic-scaling)
or creating [extra sidekiq processes](../administration/operations/extra_sidekiq_processes.md)
-NOTE: **Note**:
-After indexing the repositories asynchronously, you **MUST** index the database to be able to search.
-
Configure Elasticsearch's host and port in **Admin > Settings > Integrations**. Then create empty indexes using one of the following commands:
```sh
@@ -217,78 +214,49 @@ curl --request PUT localhost:9200/gitlab-production/_settings --data '{
} }'
```
-Then enable Elasticsearch indexing and run repository indexing tasks:
+Then enable Elasticsearch indexing and run project indexing tasks:
```sh
# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories_async
+sudo gitlab-rake gitlab:elastic:index_projects
# Installations from source
-bundle exec rake gitlab:elastic:index_repositories_async RAILS_ENV=production
+bundle exec rake gitlab:elastic:index_projects RAILS_ENV=production
```
-This enqueues a number of Sidekiq jobs to index your existing repositories.
-You can view the jobs in the admin panel (they are placed in the `elastic_batch_project_indexer`)
+This enqueues a Sidekiq job for each project that needs to be indexed.
+You can view the jobs in the admin panel (they are placed in the `elastic_indexer`
queue), or you can query indexing status using a rake task:
```sh
# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories_status
+sudo gitlab-rake gitlab:elastic:index_projects_status
# Installations from source
-bundle exec rake gitlab:elastic:index_repositories_status RAILS_ENV=production
+bundle exec rake gitlab:elastic:index_projects_status RAILS_ENV=production
Indexing is 65.55% complete (6555/10000 projects)
```
-By default, one job is created for every 300 projects. For large numbers of
-projects, you may wish to increase the batch size, by setting the `BATCH`
-environment variable.
-
-You can also run the initial indexing synchronously - this is most useful if
-you have a small number of projects or need finer-grained control over indexing
-than Sidekiq permits:
+If you want to limit the index to a range of projects you can provide the
+`ID_FROM` and `ID_TO` parameters:
```sh
# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories
+sudo gitlab-rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000
# Installations from source
-bundle exec rake gitlab:elastic:index_repositories RAILS_ENV=production
-```
-
-It might take a while depending on how big your Git repositories are.
-
-If you want to run several tasks in parallel (probably in separate terminal
-windows) you can provide the `ID_FROM` and `ID_TO` parameters:
-
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=1001 ID_TO=2000
-
-# Installations from source
-bundle exec rake gitlab:elastic:index_repositories ID_FROM=1001 ID_TO=2000 RAILS_ENV=production
+bundle exec rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000 RAILS_ENV=production
```
Where `ID_FROM` and `ID_TO` are project IDs. Both parameters are optional.
-As an example, if you have 3,000 repositories and you want to run three separate indexing tasks, you might run:
+The above examples will index all projects starting with ID `1001` up to (and including) ID `2000`.
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories ID_TO=1000
-sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=1001 ID_TO=2000
-sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=2001
-
-# Installations from source
-bundle exec rake gitlab:elastic:index_repositories RAILS_ENV=production ID_TO=1000
-bundle exec rake gitlab:elastic:index_repositories RAILS_ENV=production ID_FROM=1001 ID_TO=2000
-bundle exec rake gitlab:elastic:index_repositories RAILS_ENV=production ID_FROM=2001
-```
-
-Sometimes your repository index process `gitlab:elastic:index_repositories` or
-`gitlab:elastic:index_repositories_async` can get interrupted. This may happen
-for many reasons, but it's always safe to run the indexing job again - it will
-skip those repositories that have already been indexed.
+TIP: **Troubleshooting:**
+Sometimes the project indexing jobs queued by `gitlab:elastic:index_projects`
+can get interrupted. This may happen for many reasons, but it's always safe
+to run the indexing task again - it will skip those repositories that have
+already been indexed.
As the indexer stores the last commit SHA of every indexed repository in the
database, you can run the indexer with the special parameter `UPDATE_INDEX` and
@@ -297,10 +265,10 @@ that repository is indexed, it can be useful in case if your index is outdated:
```sh
# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories UPDATE_INDEX=true ID_TO=1000
+sudo gitlab-rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000
# Installations from source
-bundle exec rake gitlab:elastic:index_repositories UPDATE_INDEX=true ID_TO=1000 RAILS_ENV=production
+bundle exec rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000 RAILS_ENV=production
```
You can also use the `gitlab:elastic:clear_index_status` Rake task to force the
@@ -320,16 +288,6 @@ bundle exec rake gitlab:elastic:index_wikis RAILS_ENV=production
The wiki indexer also supports the `ID_FROM` and `ID_TO` parameters if you want
to limit a project set.
-Index all database entities (Keep in mind it can take a while, so consider using `screen` or `tmux`):
-
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_database
-
-# Installations from source
-bundle exec rake gitlab:elastic:index_database RAILS_ENV=production
-```
-
Enable replication and refreshing again after indexing (only if you previously disabled it):
```bash
@@ -376,25 +334,15 @@ There are several rake tasks available to you via the command line:
- This is a wrapper task. It does the following:
- `sudo gitlab-rake gitlab:elastic:create_empty_index`
- `sudo gitlab-rake gitlab:elastic:clear_index_status`
+ - `sudo gitlab-rake gitlab:elastic:index_projects`
- `sudo gitlab-rake gitlab:elastic:index_wikis`
- - `sudo gitlab-rake gitlab:elastic:index_database`
- - `sudo gitlab-rake gitlab:elastic:index_repositories`
-- [sudo gitlab-rake gitlab:elastic:index_repositories_async](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - This iterates over all projects and places them in batches. It then sends these batches to the background via sidekiq jobs to be indexed.
-- [sudo gitlab-rake gitlab:elastic:index_repositories_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - `sudo gitlab-rake gitlab:elastic:index_snippets`
+- [sudo gitlab-rake gitlab:elastic:index_projects](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - This iterates over all projects and queues sidekiq jobs to index them in the background.
+- [sudo gitlab-rake gitlab:elastic:index_projects_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100.
-- [sudo gitlab-rake gitlab:elastic:index_repositories](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - This iterates over all projects and places them in batches. It then performs indexing on said batches synchronously.
- [sudo gitlab-rake gitlab:elastic:index_wikis](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Iterates over every project, determines if said project contains wiki data, and then indexes the blobs (content) of said wiki data.
-- [sudo gitlab-rake gitlab:elastic:index_database](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - This is a [rake multitask](https://www.rubydoc.info/github/ruby/rake/Rake/MultiTask). It does the following:
- - `sudo gitlab-rake gitlab:elastic:index_projects`
- - `sudo gitlab-rake gitlab:elastic:index_issues`
- - `sudo gitlab-rake gitlab:elastic:index_merge_requests`
- - `sudo gitlab-rake gitlab:elastic:index_snippets`
- - `sudo gitlab-rake gitlab:elastic:index_notes`
- - `sudo gitlab-rake gitlab:elastic:index_milestones`
- [sudo gitlab-rake gitlab:elastic:create_empty_index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This generates an empty index on the Elasticsearch side.
- [sudo gitlab-rake gitlab:elastic:clear_index_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
@@ -405,18 +353,8 @@ There are several rake tasks available to you via the command line:
- Does the same thing as `sudo gitlab-rake gitlab:elastic:create_empty_index`
- [sudo gitlab-rake gitlab:elastic:add_feature_visibility_levels_to_project](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Adds visibility information to the indices for projects.
-- [sudo gitlab-rake gitlab:elastic:index_projects](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes projects data.
-- [sudo gitlab-rake gitlab:elastic:index_issues](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes issues data.
-- [sudo gitlab-rake gitlab:elastic:index_merge_requests](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes merge requests data.
- [sudo gitlab-rake gitlab:elastic:index_snippets](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Performs an Elasticsearch import that indexes the snippets data.
-- [sudo gitlab-rake gitlab:elastic:index_notes](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes the notes data.
-- [sudo gitlab-rake gitlab:elastic:index_milestones](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes the milestones data.
### Environment Variables
@@ -424,40 +362,16 @@ In addition to the rake tasks, there are some environment variables that can be
| Environment Variable | Data Type | What it does |
| -------------------- |:---------:| ---------------------------------------------------------------------------- |
-| `BATCH` | Integer | Modifies the size of the indexing batch (default 300 projects). |
| `UPDATE_INDEX` | Boolean | Tells the indexer to overwrite any existing index data (true/false). |
| `ID_TO` | Integer | Tells the indexer to only index projects less than or equal to the value. |
| `ID_FROM` | Integer | Tells the indexer to only index projects greater than or equal to the value. |
-### Batching
-
-The ability to apply batching makes the indexer run more efficiently. The default
-size of a batch is 300 projects, which may or may not be ideal for your setup.
-Depending on the resources available to your GitLab instance (sidekiq) and your
-Elasticsearch instance (RAM, CPU), you may be able to increase or decrease the
-batch size for more efficiency.
-
-- The larger the batch size is, the less sidekiq jobs and indexing requests get created.
-- The larger the batch size is, the more time and RAM it takes to process.
-- The smaller the batch size, the more sidekiq jobs, and indexing requests get created.
-- The smaller the batch size, the more CPU gets utilized.
-
-Finding the ideal size can be tricky, and will vary from GitLab instance to GitLab instance.
-Generally speaking, if the default is not ideal for you, try reducing it to somewhere in
-the 50-150 range (for bigger sized repos) or 450-600 range (for many small-sized repos).
-
-Example use:
-
-```sh
-sudo gitlab-rake gitlab:elastic:index_repositories_async BATCH=50
-```
-
### Indexing a specific project
Because the `ID_TO` and `ID_FROM` environment variables use the `or equal to` comparison, you can index only one project by using both these variables with the same project ID number:
```sh
-root@git:~# sudo gitlab-rake gitlab:elastic:index_repositories ID_TO=5 ID_FROM=5
+root@git:~# sudo gitlab-rake gitlab:elastic:index_projects ID_TO=5 ID_FROM=5
Indexing project repositories...I, [2019-03-04T21:27:03.083410 #3384] INFO -- : Indexing GitLab User / test (ID=33)...
I, [2019-03-04T21:27:05.215266 #3384] INFO -- : Indexing GitLab User / test (ID=33) is done!
```
@@ -554,7 +468,7 @@ Here are some common pitfalls and how to overcome them:
- **The indexing process is taking a very long time**
- The more data present in your GitLab instance, the longer the indexing process takes. You might want to try adjusting the BATCH sizes for asynchronous indexing to help speed up the process.
+ The more data present in your GitLab instance, the longer the indexing process takes.
- **No new data is added to the Elasticsearch index when I push code**
diff --git a/doc/topics/git/how_to_install_git/index.md b/doc/topics/git/how_to_install_git/index.md
index d7e1979217e..c7bede2d269 100644
--- a/doc/topics/git/how_to_install_git/index.md
+++ b/doc/topics/git/how_to_install_git/index.md
@@ -5,14 +5,20 @@ level: beginner
article_type: user guide
date: 2017-05-15
description: 'This article describes how to install Git on macOS, Ubuntu Linux and Windows.'
+type: howto
+last_updated: 2019-05-31
---
# Installing Git
-To begin contributing to GitLab projects
+To begin contributing to GitLab projects,
you will need to install the Git client on your computer.
+
This article will show you how to install Git on macOS, Ubuntu Linux and Windows.
+Information on [installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+is also available at the official Git website.
+
## Install Git on macOS using the Homebrew package manager
Although it is easy to use the version of Git shipped with macOS
@@ -21,7 +27,7 @@ we recommend installing it via Homebrew to get access to
an extensive selection of dependency managed libraries and applications.
If you are sure you don't need access to any additional development libraries
-or don't have approximately 15gb of available disk space for Xcode and Homebrew
+or don't have approximately 15gb of available disk space for Xcode and Homebrew,
use one of the aforementioned methods.
### Installing Xcode
@@ -40,11 +46,12 @@ for the official Homebrew installation instructions.
With Homebrew installed you are now ready to install Git.
Open a Terminal and enter in the following command:
-```bash
+```sh
brew install git
```
Congratulations you should now have Git installed via Homebrew.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
## Install Git on Ubuntu Linux
@@ -55,16 +62,30 @@ it is recommended to use the built in package manager to install Git.
Open a Terminal and enter in the following commands
to install the latest Git from the official Git maintained package archives:
-```bash
+```sh
sudo apt-add-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
```
Congratulations you should now have Git installed via the Ubuntu package manager.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
## Installing Git on Windows from the Git website
Browse to the [Git website](https://git-scm.com/) and download and install Git for Windows.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
+
+<!-- ## 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/topics/git/index.md b/doc/topics/git/index.md
index db6d1a62f59..841746cc5de 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -1,8 +1,12 @@
-# Git documentation
+---
+type: index
+---
+
+# Git
Git is a [free and open source](https://git-scm.com/about/free-and-open-source)
distributed version control system designed to handle everything from small to
-very large projects with speed and efficiency.
+large projects with speed and efficiency.
[GitLab](https://about.gitlab.com) is a Git-based fully integrated platform for
software development. Besides Git's functionalities, GitLab has a lot of
@@ -11,64 +15,71 @@ powerful [features](https://about.gitlab.com/features/) to enhance your
We've gathered some resources to help you to get the best from Git with GitLab.
+More information is also available on the [Git website](https://git-scm.com).
+
## Getting started
-- [Git concepts](../../university/training/user_training.md#git-concepts)
+The following resources will help you get started with Git:
+
+- [Git Basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)
+- [Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
- [How to install Git](how_to_install_git/index.md)
- [Start using Git on the command line](../../gitlab-basics/start-using-git.md)
- [Command Line basic commands](../../gitlab-basics/command-line-commands.md)
- [GitLab Git Cheat Sheet (download)](https://about.gitlab.com/images/press/git-cheat-sheet.pdf)
-- Commits
- - [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit)
+- Commits:
+ - [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit)
- [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
- [Squashing commits](../../workflow/gitlab_flow.md#squashing-commits-with-rebase)
-**Third-party references:**
+### Concepts
-- [Getting Started - Git website](https://git-scm.com)
-- [Getting Started - Version control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control)
-- [Getting Started - Git Basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)
-- [Getting Started - Installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
-- [Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
+The following are resources about version control concepts:
-### Concepts
+- [Git concepts](../../university/training/user_training.md#git-concepts)
+- [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/)
+- [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
+- [Git website topic about version control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control)
+- [GitLab University presentation about Version Control](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit?usp=sharing)
-- Article (2017-05-17): [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/)
-- Article (2016-05-11): [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
-- GLU Course (Presentation): [About Version Control](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit?usp=sharing)
+## Git tips
-## Exploring Git
+The following resources may help you become more efficient at using Git:
- [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/)
- [Eight Tips to help you work better with Git](https://about.gitlab.com/2015/02/19/8-tips-to-help-you-work-better-with-git/)
## Troubleshooting Git
+If you have problems with Git, the following may help:
+
- [Numerous _undo_ possibilities in Git](numerous_undo_possibilities_in_git/index.md)
-- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques to help you out.
+- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques
## Branching strategies
-- [GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)
-
-**Third-party references:**
-
- [Git Branching - Branches in a Nutshell](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)
- [Git Branching - Branching Workflows](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows)
+- [GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)
## Advanced use
+The following are advanced topics for those who want to get the most out of Git:
+
- [Custom Git Hooks](../../administration/custom_hooks.md)
- [Git Attributes](../../user/project/git_attributes.md)
- Git Submodules: [Using Git submodules with GitLab CI](../../ci/git_submodules.md#using-git-submodules-with-gitlab-ci)
## API
-- [Gitignore templates](../../api/templates/gitignores.md)
+[Gitignore templates](../../api/templates/gitignores.md) API allow for
+Git-related queries from GitLab.
## Git LFS
+The following relate to Git Large File Storage:
+
- [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/)
- [GitLab Git LFS documentation](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
- [Git-Annex to Git-LFS migration guide](../../workflow/lfs/migrate_from_git_annex_to_git_lfs.md)
-- Article (2015-08-13): [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
+- [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
diff --git a/doc/topics/git/numerous_undo_possibilities_in_git/index.md b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
index 8a8021dc36d..84201e11831 100644
--- a/doc/topics/git/numerous_undo_possibilities_in_git/index.md
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
@@ -4,25 +4,30 @@ author_gitlab: Letme
level: intermediary
article_type: tutorial
date: 2017-05-15
+type: howto
+last_updated: 2019-05-31
---
# Numerous undo possibilities in Git
-## Introduction
-
In this tutorial, we will show you different ways of undoing your work in Git, for which
we will assume you have a basic working knowledge of. Check GitLab's
-[Git documentation](../index.md#git-documentation) for reference.
+[Git documentation](../index.md) for reference.
+
Also, we will only provide some general info of the commands, which is enough
-to get you started for the easy cases/examples, but for anything more advanced please refer to the [Git book](https://git-scm.com/book/en/v2).
+to get you started for the easy cases/examples, but for anything more advanced
+please refer to the [Git book](https://git-scm.com/book/en/v2).
We will explain a few different techniques to undo your changes based on the stage
of the change in your current development. Also, keep in mind that [nothing in
-Git is really deleted.][git-autoclean-ref]
+Git is really deleted][git-autoclean-ref].
+
This means that until Git automatically cleans detached commits (which cannot be
accessed by branch or tag) it will be possible to view them with `git reflog` command
and access them with direct commit-id. Read more about _[redoing the undo](#redoing-the-undo)_ on the section below.
+## Introduction
+
This guide is organized depending on the [stage of development][git-basics]
where you want to undo your changes from and if they were shared with other developers
or not. Because Git is tracking changes a created or edited file is in the unstaged state
@@ -31,35 +36,41 @@ a file into the **staged** state, which is then committed (`git commit`) to your
local repository. After that, file can be shared with other developers (`git push`).
Here's what we'll cover in this tutorial:
- - [Undo local changes](#undo-local-changes) which were not pushed to remote repository
+- [Undo local changes](#undo-local-changes) which were not pushed to remote repository:
- - Before you commit, in both unstaged and staged state
- - After you committed
+ - Before you commit, in both unstaged and staged state.
+ - After you committed.
- - Undo changes after they are pushed to remote repository
+- Undo changes after they are pushed to remote repository:
- - [Without history modification](#undo-remote-changes-without-changing-history) (preferred way)
- - [With history modification](#undo-remote-changes-with-modifying-history) (requires
- coordination with team and force pushes).
- - [Usecases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable)
- - [How to modify history](#how-modifying-history-is-done)
- - [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits)
+ - [Without history modification](#undo-remote-changes-without-changing-history) (preferred way).
+ - [With history modification](#undo-remote-changes-with-modifying-history) (requires
+ coordination with team and force pushes).
+ - [Use cases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable).
+ - [How to modify history](#how-modifying-history-is-done).
+ - [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits).
### Branching strategy
[Git][git-official] is a de-centralized version control system, which means that beside regular
versioning of the whole repository, it has possibilities to exchange changes
-with other repositories. To avoid chaos with
+with other repositories.
+
+To avoid chaos with
[multiple sources of truth][git-distributed], various
development workflows have to be followed, and it depends on your internal
workflow how certain changes or commits can be undone or changed.
+
[GitLab Flow][gitlab-flow] provides a good
balance between developers clashing with each other while
developing the same feature and cooperating seamlessly, but it does not enable
joined development of the same feature by multiple developers by default.
+
When multiple developers develop the same feature on the same branch, clashing
with every synchronization is unavoidable, but a proper or chosen Git Workflow will
-prevent that anything is lost or out of sync when feature is complete. You can also
+prevent that anything is lost or out of sync when feature is complete.
+
+You can also
read through this blog post on [Git Tips & Tricks][gitlab-git-tips-n-tricks]
to learn how to easily **do** things in Git.
@@ -97,19 +108,19 @@ no changes added to commit (use "git add" and/or "git commit -a")
At this point there are 3 options to undo the local changes you have:
-- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes)
+- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes):
```shell
git stash
```
-- Discarding local changes (permanently) to a file
+- Discarding local changes (permanently) to a file:
```shell
git checkout -- <file>
```
-- Discard all local changes to all files permanently
+- Discard all local changes to all files permanently:
```shell
git reset --hard
@@ -150,7 +161,7 @@ of the staging tree. You also have an option to discard all changes with
Lets start the example by editing a file, with your favorite editor, to change the
content and add it to staging
-```
+```sh
vim <file>
git add <file>
```
@@ -164,30 +175,30 @@ Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
- new file: <file>
+ new file: <file>
```
Now you have 4 options to undo your changes:
-- Unstage the file to current commit (HEAD)
+- Unstage the file to current commit (HEAD):
```shell
git reset HEAD <file>
```
-- Unstage everything - retain changes
+- Unstage everything - retain changes:
```shell
git reset
```
-- Discard all local changes, but save them for [later](#quickly-save-local-changes)
+- Discard all local changes, but save them for [later](#quickly-save-local-changes):
```shell
git stash
```
-- Discard everything permanently
+- Discard everything permanently:
```shell
git reset --hard
@@ -206,7 +217,9 @@ your code, you'll have less options to troubleshoot your work.
Through the development process some of the previously committed changes do not
fit anymore in the end solution, or are source of the bugs. Once you find the
commit which triggered bug, or once you have a faulty commit, you can simply
-revert it with `git revert commit-id`. This command inverts (swaps) the additions and
+revert it with `git revert commit-id`.
+
+This command inverts (swaps) the additions and
deletions in that commit, so that it does not modify history. Retaining history
can be helpful in future to notice that some changes have been tried
unsuccessfully in the past.
@@ -225,19 +238,19 @@ through simple bisection process. You can read more about it [in official Git To
In our example we will end up with commit `B`, that introduced bug/error. We have
4 options on how to remove it (or part of it) from our repository.
-- Undo (swap additions and deletions) changes introduced by commit `B`.
+- Undo (swap additions and deletions) changes introduced by commit `B`:
```shell
git revert commit-B-id
```
-- Undo changes on a single file or directory from commit `B`, but retain them in the staged state
+- Undo changes on a single file or directory from commit `B`, but retain them in the staged state:
```shell
git checkout commit-B-id <file>
```
-- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state
+- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state:
```shell
git reset commit-B-id <file>
@@ -246,7 +259,9 @@ In our example we will end up with commit `B`, that introduced bug/error. We hav
- There is one command we also must not forget: **creating a new branch**
from the point where changes are not applicable or where the development has hit a
dead end. For example you have done commits `A-B-C-D` on your feature-branch
- and then you figure `C` and `D` are wrong. At this point you either reset to `B`
+ and then you figure `C` and `D` are wrong.
+
+ At this point you either reset to `B`
and do commit `F` (which will cause problems with pushing and if forced pushed also with other developers)
since branch now looks `A-B-F`, which clashes with what other developers have locally (you will
[change history](#with-history-modification)), or you simply checkout commit `B` create
@@ -269,13 +284,13 @@ In our example we will end up with commit `B`, that introduced bug/error. We hav
There is one command for history modification and that is `git rebase`. Command
provides interactive mode (`-i` flag) which enables you to:
- - **reword** commit messages (there is also `git commit --amend` for editing
- last commit message)
- - **edit** the commit content (changes introduced by commit) and message
- - **squash** multiple commits into a single one, and have a custom or aggregated
- commit message
- - **drop** commits - simply delete them
- - and few more options
+- **reword** commit messages (there is also `git commit --amend` for editing
+ last commit message).
+- **edit** the commit content (changes introduced by commit) and message.
+- **squash** multiple commits into a single one, and have a custom or aggregated
+ commit message.
+- **drop** commits - simply delete them.
+- and few more options.
Let us check few examples. Again there are commits `A-B-C-D` where you want to
delete commit `B`.
@@ -301,7 +316,7 @@ In case you want to modify something introduced in commit `B`.
- Command opens your favorite text editor where you write `edit` in front of commit
`B`, but leave default `pick` with all other commits. Save and exit the editor to
- perform a rebase
+ perform a rebase.
- Now do your edits and commit changes:
@@ -348,7 +363,9 @@ and then on end description of that action.
This topic is roughly same as modifying committed local changes without modifying
history. **It should be the preferred way of undoing changes on any remote repository
or public branch.** Keep in mind that branching is the best solution when you want
-to retain the history of faulty development, yet start anew from certain point. Branching
+to retain the history of faulty development, yet start anew from certain point.
+
+Branching
enables you to include the existing changes in new development (by merging) and
it also provides a clear timeline and development structure.
@@ -386,12 +403,14 @@ the cleanup of detached commits (happens automatically).
Modified history breaks the development chain of other developers, as changed
history does not have matching commits'ids. For that reason it should not
be used on any public branch or on branch that *might* be used by other
-developers. When contributing to big open source repositories (e.g. [GitLab CE][gitlab-ce]),
+developers. When contributing to big open source repositories (for example, [GitLab CE][gitlab-ce]),
it is acceptable to *squash* commits into a single one, to present
a nicer history of your contribution.
+
Keep in mind that this also removes the comments attached to certain commits
in merge requests, so if you need to retain traceability in GitLab, then
modifying history is not acceptable.
+
A feature-branch of a merge request is a public branch and might be used by
other developers, but project process and rules might allow or require
you to use `git rebase` (command that changes history) to reduce number of
@@ -400,8 +419,8 @@ GitLab). There is a `git merge --squash` command which does exactly that
(squashes commits on feature-branch to a single commit on target branch
at merge).
->**Note:**
-Never modify the commit history of `master` or shared branch
+NOTE: **Note:**
+Never modify the commit history of `master` or shared branch.
### How modifying history is done
@@ -436,7 +455,7 @@ pick <commit3-id> <commit3-commit-message>
# Note that empty commits are commented out
```
->**Note:**
+NOTE: **Note:**
It is important to notice that comment from the output clearly states that, if
you decide to abort, then do not just close your editor (as that will in-fact
modify history), but remove all uncommented lines and save.
@@ -470,7 +489,7 @@ tools that can use some of Git specifics to enable faster execution of common
tasks (which is exactly what removing sensitive information file is about).
An alternative is [BFG Repo-cleaner][bfg-repo-cleaner]. Keep in mind that these
tools are faster because they do not provide a same fully feature set as `git filter-branch`
-does, but focus on specific usecases.
+does, but focus on specific use cases.
## Conclusion
@@ -480,6 +499,18 @@ depending on the stage of your process. Git also enables rewriting history, but
should be avoided as it might cause problems when multiple developers are
contributing to the same codebase.
+<!-- ## 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. -->
+
<!-- Identifiers, in alphabetical order -->
[bfg-repo-cleaner]: https://rtyley.github.io/bfg-repo-cleaner/
diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md
index 71651fcf421..417d91bf834 100644
--- a/doc/topics/git/troubleshooting_git.md
+++ b/doc/topics/git/troubleshooting_git.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Troubleshooting Git
Sometimes things don't work the way they should or as you might expect when
@@ -9,7 +13,7 @@ with Git.
'Broken pipe' errors can occur when attempting to push to a remote repository.
When pushing you will usually see:
-```
+```text
Write failed: Broken pipe
fatal: The remote end hung up unexpectedly
```
@@ -39,14 +43,15 @@ There's another option where you can prevent session timeouts by configuring
SSH 'keep alive' either on the client or on the server (if you are a GitLab
admin and have access to the server).
-NOTE: **Note:** configuring *both* the client and the server is unnecessary.
+NOTE: **Note:**
+Configuring *both* the client and the server is unnecessary.
**To configure SSH on the client side**:
-- On UNIX, edit `~/.ssh/config` (create the file if it doesn’t exist) and
- add or edit:
+- On UNIX, edit `~/.ssh/config` (create the file if it doesn’t exist) and
+ add or edit:
- ```
+ ```text
Host your-gitlab-instance-url.com
ServerAliveInterval 60
ServerAliveCountMax 5
@@ -58,7 +63,7 @@ NOTE: **Note:** configuring *both* the client and the server is unnecessary.
**To configure SSH on the server side**, edit `/etc/ssh/sshd_config` and add:
-```
+```text
ClientAliveInterval 60
ClientAliveCountMax 5
```
@@ -83,35 +88,35 @@ to >= 2.9 (see [Broken pipe when pushing to Git repository][Broken-Pipe]).
Users may experience the following error when attempting to push or pull
using Git over SSH:
-```
-Please make sure you have the correct access rights
-and the repository exists.
+```text
+Please make sure you have the correct access rights
+and the repository exists.
...
-ssh_exchange_identification: read: Connection reset by peer
-fatal: Could not read from remote repository.
+ssh_exchange_identification: read: Connection reset by peer
+fatal: Could not read from remote repository.
```
This error usually indicates that SSH daemon's `MaxStartups` value is throttling
-SSH connections. This setting specifies the maximum number of unauthenticated
+SSH connections. This setting specifies the maximum number of unauthenticated
connections to the SSH daemon. This affects users with proper authentication
credentials (SSH keys) because every connection is 'unauthenticated' in the
-beginning. The default value is `10`.
+beginning. The default value is `10`.
Increase `MaxStartups` by adding or modifying the value in `/etc/ssh/sshd_config`:
-```
+```text
MaxStartups 100
```
-Restart SSHD for the change to take effect.
+Restart SSHD for the change to take effect.
## Timeout during git push/pull
If pulling/pushing from/to your repository ends up taking more than 50 seconds,
-a timeout will be issued with a log of the number of operations performed
+a timeout will be issued with a log of the number of operations performed
and their respective timings, like the example below:
-```
+```text
remote: Running checks for branch: master
remote: Scanning for LFS objects... (153ms)
remote: Calculating new repository size... (cancelled after 729ms)
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index 82a86f3f343..f82d666c7be 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -114,7 +114,47 @@ sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.10.5.linux-amd64.tar.gz
```
-### 6. Get latest code
+### 6. Update git
+
+NOTE: **Note:**
+GitLab 11.11 and higher only supports Git 2.21.x and newer, and
+[dropped support for older versions](https://gitlab.com/gitlab-org/gitlab-ce/issues/54255).
+Be sure to upgrade your installation if necessary.
+
+```bash
+# Make sure Git is version 2.21.0 or higher
+git --version
+
+# Remove packaged Git
+sudo apt-get remove git-core
+
+# Install dependencies
+sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential
+
+# Download and compile pcre2 from source
+curl --silent --show-error --location https://ftp.pcre.org/pub/pcre/pcre2-10.33.tar.gz --output pcre2.tar.gz
+tar -xzf pcre2.tar.gz
+cd pcre2-10.33
+chmod +x configure
+./configure --prefix=/usr --enable-jit
+make
+make install
+
+# Download and compile from source
+cd /tmp
+curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.21.0.tar.gz
+echo '85eca51c7404da75e353eba587f87fea9481ba41e162206a6f70ad8118147bee git-2.21.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.21.0.tar.gz
+cd git-2.21.0/
+./configure --with-libpcre
+make prefix=/usr/local all
+
+# Install into /usr/local/bin
+sudo make prefix=/usr/local install
+
+# You should edit config/gitlab.yml, change the git -> bin_path to /usr/local/bin/git
+```
+
+### 7. Get latest code
```bash
cd /home/git/gitlab
@@ -142,7 +182,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout BRANCH-ee
```
-### 7. Update gitlab-shell
+### 8. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
@@ -152,7 +192,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
sudo -u git -H bin/compile
```
-### 8. Update gitlab-workhorse
+### 9. Update gitlab-workhorse
Install and compile gitlab-workhorse. GitLab-Workhorse uses
[GNU Make](https://www.gnu.org/software/make/).
@@ -167,7 +207,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
sudo -u git -H make
```
-### 9. Update Gitaly
+### 10. Update Gitaly
#### Compile Gitaly
@@ -178,7 +218,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 10. Update gitlab-pages
+### 11. Update gitlab-pages
#### Only needed if you use GitLab Pages
@@ -195,7 +235,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make
```
-### 11. Update MySQL permissions
+### 12. Update MySQL permissions
If you are using MySQL you need to grant the GitLab user the necessary
permissions on the database:
@@ -217,7 +257,7 @@ You can make this setting permanent by adding it to your `my.cnf`:
log_bin_trust_function_creators=1
```
-### 12. Update configuration files
+### 13. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -291,7 +331,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 13. Install libs, migrations, etc.
+### 14. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -323,14 +363,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
**MySQL installations**: Run through the `MySQL strings limits` and `Tables and
data conversion to utf8mb4` [tasks](../install/database_mysql.md).
-### 14. Start application
+### 15. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 15. Check application status
+### 16. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md
index 1d355824760..001e4b6bf48 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -40,11 +40,9 @@ These settings can be found within:
- **Admin Area > Settings > General**.
- The path `/admin/application_settings`.
-The very first push of a new project cannot be checked for size as of now, so
-the first push will allow you to upload more than the limit dictates, but every
-subsequent push will be denied. LFS objects, however, can be checked on first
-push and **will** be rejected if the sum of their sizes exceeds the maximum
-allowed repository size.
+The first push of a new project, including LFS objects, will be checked for size
+and **will** be rejected if the sum of their sizes exceeds the maximum allowed
+repository size.
For details on manually purging files, see [reducing the repository size using Git](../../project/repository/reducing_the_repo_size_using_git.md).
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 0e697b9e445..eed087ae52b 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -1,9 +1,13 @@
-# Admin area settings **[CORE ONLY]**
+---
+type: index
+---
-In the admin area settings, you can find various options for your GitLab
+# Admin Area settings **[CORE ONLY]**
+
+In the Admin Area **Settings** page, you can find various options for your GitLab
instance like sign-up restrictions, account limits and quota, metrics, etc.
-Navigate to it by going to **Admin area > Settings**. Some of the settings
+Navigate to it by going to **Admin Area > Settings**. Some of the settings
include:
- [Account and limit settings](account_and_limit_settings.md) **[STARTER]**
@@ -22,7 +26,7 @@ in the **Localization** section of **Admin area > Settings > Preferences**.
## GitLab.com admin area settings
-Most of the settings under the admin area change the behavior of the whole
+Most of the settings under the Admin Area change the behavior of the whole
GitLab instance. For GitLab.com, the admin settings are available only for the
GitLab.com administrators, and the parameters can be found on the
[GitLab.com settings](../../gitlab_com/index.md) documentation.
diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
index 4d1b7d0f252..a1229484388 100644
--- a/doc/user/admin_area/settings/visibility_and_access_controls.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -1,8 +1,25 @@
+---
+type: reference
+---
+
# Visibility and access controls
+GitLab allows admins to:
+
+- Control access and visibility to GitLab resources including branches and projects.
+- Select from which hosting sites code can be imported into GitLab.
+- Select the protocols permitted to access GitLab.
+- Enable or disable repository mirroring.
+
+To access the visibility and access control options:
+
+1. Log in to GitLab as an admin.
+1. Go to **Admin Area > Settings > General**.
+1. Expand the **Visibility and access controls** section.
+
## Import sources
-Choose from which hosting sites the users can
+Choose from which hosting sites users can
[import their projects](../../project/import/index.md).
![import sources](img/import_sources.png)
@@ -11,12 +28,10 @@ Choose from which hosting sites the users can
> [Introduced][ce-4696] in GitLab 8.10.
-With GitLab's Access restrictions you can choose which Git access protocols you
-want your users to use to communicate with GitLab. This feature can be enabled
-via the `Application Settings` in the Admin interface.
+With GitLab's access restrictions, you can select with which protocols users can communicate with
+GitLab.
-The setting is called `Enabled Git access protocols`, and it gives you the option
-to choose between:
+From the **Enabled Git access protocols** dropdown, select one of the following:
- Both SSH and HTTP(S)
- Only SSH
@@ -24,10 +39,9 @@ to choose between:
![Settings Overview](img/access_restrictions.png)
-When both SSH and HTTP(S) are enabled, GitLab will behave as usual, it will give
-your users the option to choose which protocol they would like to use.
+When both SSH and HTTP(S) are enabled, your users can choose either protocol.
-When you choose to allow only one of the protocols, a couple of things will happen:
+When only one protocol is enabled:
- The project page will only show the allowed protocol's URL, with no option to
change it.
@@ -54,10 +68,22 @@ application level.
> [Introduced][ee-3586] in GitLab 10.3.
This option is enabled by default. By disabling it, both pull and push mirroring will no longer
-work in every repository and can only be re-enabled on a per-project basis by an admin.
+work in every repository and can only be re-enabled by an admin on a per-project basis.
![Mirror settings](img/mirror_settings.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. -->
+
[ce-4696]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4696
[ce-18021]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18021
[ee-3586]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3586
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index abc6e771b0f..028ff72a160 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -39,6 +39,8 @@ However, DAST can be [configured](#full-scan)
to also perform a so-called "active scan". That is, attack your application and produce a more extensive security report.
It can be very useful combined with [Review Apps](../../../ci/review_apps/index.md).
+The [`dast`](https://gitlab.com/gitlab-org/security-products/dast/container_registry) Docker image in GitLab container registry is updated on a weekly basis to have all [`owasp2docker-weekly`](https://hub.docker.com/r/owasp/zap2docker-weekly/) updates in it.
+
## Use cases
It helps you automatically find security vulnerabilities in your running web
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 658d493ba39..d78cf778110 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -225,6 +225,17 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
+## Dependency List
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+
+An additional benefit of Dependency Scanning is the ability to get a list of your project's dependencies with their versions.
+
+This list can be generated only for [supported languages and package managers](#supported-languages-and-package-managers).
+
+To see the generated dependency list, navigate to the Dependency List page under your project's left sidebar menu **Project > Dependency List**.
+
## Contributing to the vulnerability database
You can search the [gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) project
diff --git a/doc/user/application_security/img/dismissed_info.png b/doc/user/application_security/img/dismissed_info.png
new file mode 100644
index 00000000000..64d5cf26ed2
--- /dev/null
+++ b/doc/user/application_security/img/dismissed_info.png
Binary files differ
diff --git a/doc/user/application_security/img/interactive_reports.png b/doc/user/application_security/img/interactive_reports.png
index 9f9812dc69d..373b39104db 100644
--- a/doc/user/application_security/img/interactive_reports.png
+++ b/doc/user/application_security/img/interactive_reports.png
Binary files differ
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 64e72fab198..679847b76d7 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -47,6 +47,16 @@ You can dismiss vulnerabilities by clicking the **Dismiss vulnerability** button
This will dismiss the vulnerability and re-render it to reflect its dismissed state.
If you wish to undo this dismissal, you can click the **Undo dismiss** button.
+#### Adding a dismissal reason
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.0.
+
+When dismissing a vulnerability, it's often helpful to provide a reason for doing so.
+If you press the comment button next to **Dismiss vulnerability** in the modal, a text box will appear, allowing you to add a comment with your dismissal.
+This comment can not currently be edited or removed, but [future versions](https://gitlab.com/gitlab-org/gitlab-ee/issues/11721) will add this functionality.
+
+![Dismissed vulnerability comment](img/dismissed_info.png)
+
### Creating an issue for a vulnerability
You can create an issue for a vulnerability by selecting the **Create issue**
diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
index 3294e59e943..f0dad6c54d0 100644
--- a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
+++ b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
Binary files differ
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index e0e89051be4..3c5e820c1ca 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -52,7 +52,7 @@ Add another cluster similar to the first one and make sure to
[set an environment scope](#environment-scopes-premium) that will
differentiate the new cluster from the rest.
-## Gitlab-managed clusters
+## GitLab-managed clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011) in GitLab 11.5.
> Became [optional](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26565) in GitLab 11.11.
@@ -149,3 +149,15 @@ The following features are not currently available for group-level clusters:
1. Terminals (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55487)).
1. Pod logs (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)).
1. Deployment boards (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55489)).
+
+<!-- ## 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/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md
index f67325272a6..aa088d2fcdb 100644
--- a/doc/user/group/custom_project_templates.md
+++ b/doc/user/group/custom_project_templates.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Custom group-level project templates **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6861) in [GitLab Premium](https://about.gitlab.com/pricing) 11.6.
@@ -24,3 +28,15 @@ Projects of nested subgroups of a selected template source cannot be used.
Repository and database information that are copied over to each new project are
identical to the data exported with [GitLab's Project Import/Export](../project/settings/import_export.md).
+
+<!-- ## 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/group/epics/index.md b/doc/user/group/epics/index.md
index 6035f0c7326..2e4106f55e5 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Epics **[ULTIMATE]**
> Introduced in [GitLab Ultimate][ee] 10.2.
@@ -215,3 +219,15 @@ Once you wrote your comment, you can either:
## Notifications
- [Receive notifications](../../../workflow/notifications.md) for epic events.
+
+<!-- ## 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/group/index.md b/doc/user/group/index.md
index eb0c7bc998f..8335e532d0b 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -9,6 +9,8 @@ With GitLab Groups, you can:
- Assemble related projects together.
- Grant members access to several projects at once.
+For a video introduction to GitLab Groups, see [GitLab University: Repositories, Projects and Groups](https://www.youtube.com/watch?v=4TWfh1aKHHw).
+
Groups can also be nested in [subgroups](subgroups/index.md).
Find your groups by clicking **Groups > Your Groups** in the top navigation.
diff --git a/doc/user/group/insights/index.md b/doc/user/group/insights/index.md
index 83c284dce7a..a4ea71074ec 100644
--- a/doc/user/group/insights/index.md
+++ b/doc/user/group/insights/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Insights **[ULTIMATE]**
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.9 behind the `insights` feature flag.
@@ -44,3 +48,15 @@ access to the project they belong to, or because they are confidential) are
filtered out of the Insights charts.
You may also consult the [group permissions table](../../permissions.md#group-members-permissions).
+
+<!-- ## 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/group/roadmap/index.md b/doc/user/group/roadmap/index.md
index 310c4bb88d0..683c715c8d5 100644
--- a/doc/user/group/roadmap/index.md
+++ b/doc/user/group/roadmap/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Roadmap **[ULTIMATE]**
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.5.
@@ -19,13 +23,20 @@ Epics in the view can be sorted by:
- **Start date**
- **Due date**
-Each option contains a button that can toggle the order between **ascending** and **descending**. The sort option and order will be persisted to be used wherever epics are browsed including the [epics list view](../epics/index.md).
+Each option contains a button that toggles the sort order between **ascending** and **descending**. The sort option and order will be persisted when browsing Epics,
+including the [epics list view](../epics/index.md).
Roadmaps can also be [visualized inside an epic](../epics/index.md#roadmap-in-epics).
## Timeline duration
-Starting with [GitLab Ultimate][ee] 11.0, Roadmap supports three different date ranges; Quarters, Months (Default) and Weeks.
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.0.
+
+Roadmap supports the following date ranges:
+
+- Quarters
+- Months (Default)
+- Weeks
### Quarters
@@ -62,3 +73,15 @@ and due date. If an epic doesn't have a due date, the timeline bar fades
away towards the future. Similarly, if an epic doesn't have a start date, the
timeline bar becomes more visible as it approaches the epic's due date on the
timeline.
+
+<!-- ## 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/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 62a3ef52c34..fcfd638f185 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# SAML SSO for GitLab.com Groups **[SILVER ONLY]**
> Introduced in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.0.
@@ -15,7 +19,7 @@ SAML SSO for GitLab.com groups does not sync users between providers without usi
## Configuring your Identity Provider
1. Navigate to the group and click **Settings > SAML SSO**.
-1. Configure your SAML server using the **Assertion consumer service URL** and **Issuer**. Alternatively GitLab provides [metadata XML configuration](#metadata-configuration). See [your identity provider's documentation](#providers) for more details.
+1. Configure your SAML server using the **Assertion consumer service URL** and **Identifier**. Alternatively GitLab provides [metadata XML configuration](#metadata-configuration). See [your identity provider's documentation](#providers) for more details.
1. Configure the SAML response to include a NameID that uniquely identifies each user.
1. Configure required assertions using the [table below](#assertions).
1. Once the identity provider is set up, move on to [configuring GitLab](#configuring-gitlab).
@@ -43,12 +47,12 @@ GitLab.com uses the SAML NameID to identify users. The NameID element:
### Assertions
-| Field | Supported keys | Notes |
-|-|----------------|-------------|
-| Email | `email`, `mail` | (required) |
-| Full Name | `name` | |
-| First Name | `first_name`, `firstname`, `firstName` | |
-| Last Name | `last_name`, `lastname`, `lastName` | |
+| Field | Supported keys |
+|-------|----------------|
+| Email (required)| `email`, `mail` |
+| Full Name | `name` |
+| First Name | `first_name`, `firstname`, `firstName` |
+| Last Name | `last_name`, `lastname`, `lastName` |
## Metadata configuration
@@ -122,3 +126,15 @@ For example, to unlink the `MyOrg` account, the following **Disconnect** button
| Assertion consumer service URL | The callback on GitLab where users will be redirected after successfully authenticating with the identity provider. |
| Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". |
| Certificate fingerprint | Used to confirm that communications over SAML are secure by checking that the server is signing communications with the correct certificate. Also known as a certificate thumbprint. |
+
+<!-- ## 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/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 4e81e28a45a..79ae94dd9ef 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -1,11 +1,17 @@
+---
+type: reference, howto, concepts
+---
+
# Subgroups
NOTE: **Note:**
[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2772) in GitLab 9.0. Not available when using MySQL as external
database (support removed in GitLab 9.3 [due to performance reasons](https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600)).
-With subgroups (aka nested groups or hierarchical groups) you can have
-up to 20 levels of nested groups, which among other things can help you to:
+Subgroups, also known as nested groups or hierarchical groups, allow you to have up to 20
+levels of groups.
+
+By using subgroups you can do the following:
- **Separate internal / external organizations.** Since every group
can have its own visibility level, you are able to host groups for different
@@ -17,8 +23,9 @@ up to 20 levels of nested groups, which among other things can help you to:
## Database Requirements
-Nested groups are only supported when you use PostgreSQL. Supporting nested
-groups on MySQL in an efficient way is not possible due to MySQL's limitations.
+Subgroups are only supported when you use PostgreSQL. Supporting subgroups on MySQL in an
+efficient way is not possible due to MySQL's limitations.
+
See the following links for more information:
- <https://gitlab.com/gitlab-org/gitlab-ce/issues/30472>
@@ -37,7 +44,7 @@ only 1 parent group. It resembles a directory behavior or a nested items list:
- Group 1.2.2.1
In a real world example, imagine maintaining a GNU/Linux distribution with the
-first group being the name of the distro and subsequent groups split like:
+first group being the name of the distribution, and subsequent groups split as follows:
- Organization Group - GNU/Linux distro
- Category Subgroup - Packages
@@ -69,22 +76,22 @@ Another example of GitLab as a company would be the following:
---
-The maximum nested groups a group can have, including the first one in the
+The maximum subgroups a group can have, including the first one in the
hierarchy, is 21.
-Things like transferring or importing a project inside nested groups, work like
+Actions such as transferring or importing a project inside subgroups, work like
when performing these actions the traditional way with the `group/project`
structure.
## Creating a subgroup
NOTE: **Note:**
-You need to be an Owner of a group in order to be able to create a subgroup. For
+You must be an Owner of a group to create a subgroup. For
more information check the [permissions table](../../permissions.md#group-members-permissions).
For a list of words that are not allowed to be used as group names see the
[reserved names](../../reserved_names.md).
Users can always create subgroups if they are explicitly added as an Owner to
-a parent group even if group creation is disabled by an administrator in their
+a parent group, even if group creation is disabled by an administrator in their
settings.
To create a subgroup:
@@ -111,7 +118,7 @@ When you add a member to a subgroup, they inherit the membership and permission
level from the parent group. This model allows access to nested groups if you
have membership in one of its parents.
-The group permissions for a member can be changed only by Owners and only on
+The group permissions for a member can be changed only by Owners, and only on
the **Members** page of the group the member was added.
You can tell if a member has inherited the permissions from a parent group by
@@ -119,24 +126,24 @@ looking at the group's **Members** page.
![Group members page](img/group_members.png)
-From the image above, we can deduct the following things:
+From the image above, we can deduce the following things:
-- There are 5 members that have access to the group `four`
+- There are 5 members that have access to the group `four`.
- User0 is a Reporter and has inherited their permissions from group `one`
- which is above the hierarchy of group `four`
+ which is above the hierarchy of group `four`.
- User1 is a Developer and has inherited their permissions from group
- `one/two` which is above the hierarchy of group `four`
+ `one/two` which is above the hierarchy of group `four`.
- User2 is a Developer and has inherited their permissions from group
- `one/two/three` which is above the hierarchy of group `four`
+ `one/two/three` which is above the hierarchy of group `four`.
- For User3 there is no indication of a parent group, therefore they belong to
- group `four`, the one we're inspecting
+ group `four`, the one we're inspecting.
- Administrator is the Owner and member of **all** subgroups and for that reason,
- same as User3, there is no indication of an ancestor group
+ as with User3, there is no indication of an ancestor group.
### Overriding the ancestor group membership
NOTE: **Note:**
-You need to be an Owner of a group in order to be able to add members to it.
+You must be an Owner of a group to be able to add members to it.
NOTE: **Note:**
A user's permissions in a subgroup cannot be lower than in any of its ancestor groups.
@@ -154,7 +161,7 @@ the permissions will fallback to those of the ancestor group.
## Mentioning subgroups
Mentioning groups (`@group`) in issues, commits and merge requests, would
-notify all members of that group. Now with subgroups, there is a more granular
+notify all members of that group. Now with subgroups, there is more granular
support if you want to split your group's structure. Mentioning works as before
and you can choose the group of people to be notified.
@@ -179,3 +186,15 @@ Here's a list of what you can't do with subgroups:
[permissions]: ../../permissions.md#group-members-permissions
[reserved]: ../../reserved_names.md
[issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600
+
+<!-- ## 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/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index 0d7bbb0af79..304a7984191 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -23,7 +23,7 @@ Here's a list of things that will **not** be deleted:
- Award emoji that the user created
Instead of being deleted, these records will be moved to a system-wide
-"Ghost User", whose sole purpose is to act as a container for such records.
+user with the username "Ghost User", whose sole purpose is to act as a container for such records. Any commits made by a deleted user will still display the username of the original user.
When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md) or spam log, these associated
records are not ghosted and will be removed, along with any groups the user
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index dfbed0ec95f..2cda850f24e 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -216,7 +216,7 @@ The sample function can now be triggered from any HTTP client using a simple `PO
--header "Content-Type: application/json" \
--request POST \
--data '{"GitLab":"FaaS"}' \
- http://functions-echo.functions-1.functions.example.com
+ http://functions-echo.functions-1.functions.example.com/
```
2. Using a web-based tool (ie. postman, restlet, etc)
diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md
index 5154ff38154..3344e560870 100644
--- a/doc/user/project/insights/index.md
+++ b/doc/user/project/insights/index.md
@@ -179,7 +179,7 @@ Supported values are:
Filter by the state of the queried "issuable".
-If you omit it, no state filter will be applied.
+If you omit it, the `opened` state filter will be applied.
Supported values are:
@@ -187,6 +187,7 @@ Supported values are:
- `closed`: Closed Open issues / merge requests.
- `locked`: Issues / merge requests that have their discussion locked.
- `merged`: Merged merge requests.
+- `all`: Issues / merge requests in all states
#### `query.filter_labels`
diff --git a/doc/user/project/integrations/img/jira_add_user_to_group.png b/doc/user/project/integrations/img/jira_add_user_to_group.png
index 27dac49260c..d8cf541a81e 100644
--- a/doc/user/project/integrations/img/jira_add_user_to_group.png
+++ b/doc/user/project/integrations/img/jira_add_user_to_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_added_user_to_group.png b/doc/user/project/integrations/img/jira_added_user_to_group.png
new file mode 100644
index 00000000000..b3e29a65d6e
--- /dev/null
+++ b/doc/user/project/integrations/img/jira_added_user_to_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_create_new_group.png b/doc/user/project/integrations/img/jira_create_new_group.png
index 06c4e84fc61..84be3a94a45 100644
--- a/doc/user/project/integrations/img/jira_create_new_group.png
+++ b/doc/user/project/integrations/img/jira_create_new_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_create_new_user.png b/doc/user/project/integrations/img/jira_create_new_user.png
index e9c03ed770d..8460dc98ef9 100644
--- a/doc/user/project/integrations/img/jira_create_new_user.png
+++ b/doc/user/project/integrations/img/jira_create_new_user.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_group_access.png b/doc/user/project/integrations/img/jira_group_access.png
index 448cc55504d..58cf114bd55 100644
--- a/doc/user/project/integrations/img/jira_group_access.png
+++ b/doc/user/project/integrations/img/jira_group_access.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_user_management_link.png b/doc/user/project/integrations/img/jira_user_management_link.png
index 5eb9d031c3e..43ef18da6c8 100644
--- a/doc/user/project/integrations/img/jira_user_management_link.png
+++ b/doc/user/project/integrations/img/jira_user_management_link.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index a90167b9767..c652149052e 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -47,11 +47,11 @@ project in Jira and then enter the correct values in GitLab.
When connecting to **JIRA Server**, which supports basic authentication, a **username and password** are required. Check the link below and proceed to the next step:
-- [Setting up an user in JIRA server](jira_server_configuration.md)
+- [Setting up a user in JIRA server](jira_server_configuration.md)
When connecting to **JIRA Cloud**, which supports authentication via API token, an **email and API token**, are required. Check the link below and proceed to the next step:
-- [Setting up an user in JIRA cloud](jira_cloud_configuration.md)
+- [Setting up a user in JIRA cloud](jira_cloud_configuration.md)
### Configuring GitLab
diff --git a/doc/user/project/integrations/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index 20036183187..13d65c4d8e4 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -1,18 +1,16 @@
# Creating a username and password for JIRA server
We need to create a user in Jira which will have access to all projects that
-need to integrate with GitLab. Login to your Jira instance as admin and under
-*Administration*, go to *User Management* and create a new user.
+need to integrate with GitLab.
As an example, we'll create a user named `gitlab` and add it to the `Jira-developers`
group.
NOTE: **Note**
-It is important that the user `gitlab` has 'write' access to projects in Jira.
+It is important that the Jira user created for the integration is given 'write'
+access to your Jira projects. This is covered in the process below.
-We have split this stage in steps so it is easier to follow.
-
-1. Log in to your Jira instance as an administrator and under **Administration**
+1. Log in to your Jira instance as an administrator and under **Jira Administration**
go to **User Management** to create a new user.
![Jira user management link](img/jira_user_management_link.png)
@@ -27,27 +25,34 @@ We have split this stage in steps so it is easier to follow.
![Jira create new user](img/jira_create_new_user.png)
-1. Create a `gitlab-developers` group which will have write access
- to projects in Jira. Go to the **Groups** tab and select **Create group**.
+1. Create a `gitlab-developers` group. (We will give this group write access to Jira
+ projects in a later step). Go to the **Groups** tab on the left, and select **Add group**.
![Jira create new user](img/jira_create_new_group.png)
- Give it an optional description and click **Create group**.
+ Give it a name and click **Add group**.
- ![Jira create new group](img/jira_create_new_group_name.png)
+1. Add the `gitlab` user to the `gitlab-developers` group by clicking **Edit members**.
+ The `gitlab-developers` group should be listed in the leftmost box as a selected group.
+ Under **Add members to selected group(s)**, enter `gitlab`.
-1. To give the newly-created group 'write' access, go to
- **Application access > View configuration** and add the `gitlab-developers`
- group to Jira Core.
+ ![Jira add user to group](img/jira_add_user_to_group.png)
+
+ Click **Add selected users** and `gitlab` should appear in the **Group member(s)** box.
+ This membership is saved automatically.
+
+ ![Jira added user to group](img/jira_added_user_to_group.png)
+
+1. To give the newly-created group 'write' access, you need to create a **Permission Scheme**.
+ To do this, click the gear icon and select **Issues**. Then click **Permission Schemes**.
+ Click **Add Permission Scheme** and enter a **Name** and, optionally, a **Description**.
+
+1. Once your permission scheme is created, you'll be taken back to the permissions scheme list.
+ Locate your new permissions scheme and click **Permissions**. Next to **Administer Projects**,
+ click **Edit**. In the resulting dialog box, select **Group** and select `gitlab-developers`
+ from the dropdown.
![Jira group access](img/jira_group_access.png)
-1. Add the `gitlab` user to the `gitlab-developers` group by going to
- **Users > GitLab user > Add group** and selecting the `gitlab-developers`
- group from the dropdown menu. Notice that the group says _Access_, which is
- intended as part of this process.
-
- ![Jira add user to group](img/jira_add_user_to_group.png)
-
The Jira configuration is complete. Write down the new Jira username and its
password as they will be needed when [configuring GitLab in the next section](jira.md#configuring-gitlab).
diff --git a/doc/user/project/integrations/youtrack.md b/doc/user/project/integrations/youtrack.md
index a2a468b6fe4..81c148e41fd 100644
--- a/doc/user/project/integrations/youtrack.md
+++ b/doc/user/project/integrations/youtrack.md
@@ -31,8 +31,8 @@ To disable the internal issue tracker in a project:
## Referencing YouTrack issues in GitLab
Issues in YouTrack can be referenced as `<PROJECT>-<ID>`. `<PROJECT>`
-must start with a capital letter and can then be followed by capital or lower case
-letters, numbers or underscores. `<ID>` is a number. An example reference is `YT-101` or `Api_32-143`.
+must start with a letter and is followed by letters, numbers, or underscores.
+`<ID>` is a number. An example reference is `YT-101`, `Api_32-143` or `gl-030`.
References to `<PROJECT>-<ID>` in merge requests, commits, or comments are automatically linked to the YouTrack issue URL.
For more information, see the [External Issue Tracker](../../../integration/external-issue-tracker.md) documentation.
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 8e9e9aa79cf..e5f62a3bb8d 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -32,6 +32,14 @@ An issue, epic, or merge request cannot have two scoped labels with the same key
For example, if an issue is already labeled `priority::3` and you apply the label `priority::2` to it,
`priority::3` is automatically removed.
+### Labels with multiple colon pairs
+
+If labels have multiple instances of `::`, the longest path from left to right, until the last `::`, is considered the "key" or the "scope".
+
+For example, `nested::key1::value1` and `nested::key1::value2` cannot both exist on the same issue. Adding the latter label will automatically remove the former due to the shared scope of `nested::key1`.
+
+`nested::key1::value1` and `nested::key2::value1` can both exist on the same issue, as these are considered to use two different label scopes, `nested::key1` and `nested::key2`.
+
### Workflows with scoped labels **[PREMIUM]**
Suppose you wanted a custom field in issues to track the platform operating system
diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md
index da6e6b5fd3a..e94125e658d 100644
--- a/doc/user/project/merge_requests/allow_collaboration.md
+++ b/doc/user/project/merge_requests/allow_collaboration.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Allow collaboration on merge requests across forks
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395)
@@ -70,3 +74,15 @@ Here's how the process would look like:
Note the colon (`:`) between the two branches. The above command will push the
local branch `thedude-awesome-project-update-docs` to the
`update-docs` branch of the `git@gitlab.com:thedude/awesome-project.git` repository.
+
+<!-- ## 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/project/merge_requests/authorization_for_merge_requests.md b/doc/user/project/merge_requests/authorization_for_merge_requests.md
index 79444ee5682..0579e3568da 100644
--- a/doc/user/project/merge_requests/authorization_for_merge_requests.md
+++ b/doc/user/project/merge_requests/authorization_for_merge_requests.md
@@ -1,8 +1,12 @@
+---
+type: concepts
+---
+
# Authorization for Merge requests
There are two main ways to have a merge request flow with GitLab:
-1. Working with [protected branches] in a single repository.
+1. Working with [protected branches](../protected_branches.md) in a single repository.
1. Working with forks of an authoritative project.
## Protected branch flow
@@ -53,4 +57,14 @@ forks.
- The project need to keep their forks up to date, which requires more advanced
Git skills (managing multiple remotes).
-[protected branches]: ../protected_branches.md
+<!-- ## 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/project/merge_requests/browser_performance_testing.md b/doc/user/project/merge_requests/browser_performance_testing.md
index 65ee2e128ae..f6c4f767e3c 100644
--- a/doc/user/project/merge_requests/browser_performance_testing.md
+++ b/doc/user/project/merge_requests/browser_performance_testing.md
@@ -1,14 +1,18 @@
+---
+type: reference, howto
+---
+
# Browser Performance Testing **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3507)
in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
-## Overview
-
If your application offers a web interface and you are using
[GitLab CI/CD](../../../ci/README.md), you can quickly determine the performance
impact of pending code changes.
+## Overview
+
GitLab uses [Sitespeed.io](https://www.sitespeed.io), a free and open source
tool for measuring the performance of web sites, and has built a simple
[Sitespeed plugin](https://gitlab.com/gitlab-org/gl-performance)
@@ -52,3 +56,15 @@ Consecutive merge requests will have something to compare to and the Performance
report will be shown properly.
![Performance Widget](img/browser_performance_testing.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/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md
index 06b3779668b..a5c191c150f 100644
--- a/doc/user/project/merge_requests/cherry_pick_changes.md
+++ b/doc/user/project/merge_requests/cherry_pick_changes.md
@@ -1,8 +1,11 @@
-# Cherry-pick changes
+---
+type: reference, concepts
+---
-> [Introduced][ce-3514] in GitLab 8.7.
+# Cherry-pick changes
-GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick]
+GitLab implements Git's powerful feature to
+[cherry-pick any commit](https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation")
with introducing a **Cherry-pick** button in merge requests and commit details.
## Cherry-picking a merge request
@@ -18,9 +21,9 @@ where you can choose to either:
- Cherry-pick the changes directly into the selected branch.
- Create a new merge request with the cherry-picked changes.
-## Cherry-picking a Commit
+## Cherry-picking a commit
-You can cherry-pick a Commit from the Commit details page:
+You can cherry-pick a commit from the commit details page:
![Cherry-pick commit](img/cherry_pick_changes_commit.png)
@@ -39,5 +42,14 @@ mainline:
git cherry-pick -m 2 7a39eb0
```
-[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request"
-[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation"
+<!-- ## 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/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
index 705ff333579..c3c2bdd94b3 100644
--- a/doc/user/project/merge_requests/code_quality.md
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -1,14 +1,16 @@
+---
+type: reference, howto
+---
+
# Code Quality **[STARTER]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1984)
in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
-## Overview
-
-If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your
+With the help of [GitLab CI/CD](../../../ci/README.md), you can analyze your
source code quality using GitLab Code Quality.
Code Quality uses [Code Climate Engines](https://codeclimate.com), which are
-free and open source. Code Quality doesn’t require a Code Climate subscription.
+free and open source. Code Quality doesn't require a Code Climate subscription.
Going a step further, GitLab can show the Code Quality report right
in the merge request widget area:
@@ -69,14 +71,24 @@ example on [analyzing a project's code quality](../../../ci/examples/code_qualit
GitLab then checks this report, compares the metrics between the source and target
branches, and shows the information right on the merge request.
-CAUTION: **Caution:**
If multiple jobs in a pipeline generate a code quality artifact, only the artifact from
the last created job (the job with the largest job ID) is used. To avoid confusion,
configure only one job to generate a code quality artifact.
-NOTE: **Note:**
If the Code Quality 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
Code Quality job in your `.gitlab-ci.yml` for the very first time.
Consecutive merge requests will have something to compare to and the Code Quality
report will be shown properly.
+
+<!-- ## 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/project/merge_requests/fast_forward_merge.md b/doc/user/project/merge_requests/fast_forward_merge.md
index 3cd91a185e3..4f3c68090e4 100644
--- a/doc/user/project/merge_requests/fast_forward_merge.md
+++ b/doc/user/project/merge_requests/fast_forward_merge.md
@@ -1,21 +1,24 @@
+---
+type: reference, concepts
+---
+
# Fast-forward merge requests
-Retain a linear Git history and a way to accept merge requests without
-creating merge commits.
+Sometimes, a workflow policy might mandate a clean commit history without
+merge commits. In such cases, the fast-forward merge is the perfect candidate.
+
+With fast-forward merge requests, you can retain a linear Git history and a way
+to accept merge requests without creating merge commits.
## Overview
-When the fast-forward merge ([`--ff-only`][ffonly]) setting is enabled, no merge
-commits will be created and all merges are fast-forwarded, which means that
-merging is only allowed if the branch could be fast-forwarded.
+When the fast-forward merge
+([`--ff-only`](https://git-scm.com/docs/git-merge#git-merge---ff-only)) setting
+is enabled, no merge commits will be created and all merges are fast-forwarded,
+which means that merging is only allowed if the branch could be fast-forwarded.
When a fast-forward merge is not possible, the user is given the option to rebase.
-## Use cases
-
-Sometimes, a workflow policy might mandate a clean commit history without
-merge commits. In such cases, the fast-forward merge is the perfect candidate.
-
## Enabling fast-forward merges
1. Navigate to your project's **Settings** and search for the 'Merge method'
@@ -32,4 +35,14 @@ source branch locally before you will be able to do a fast-forward merge.
![Fast forward merge rebase locally](img/ff_merge_rebase_locally.png)
-[ffonly]: https://git-scm.com/docs/git-merge#git-merge---ff-only
+<!-- ## 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/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 4cfe59b808a..447b338928c 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -1,7 +1,11 @@
+---
+type: index, reference, concepts
+---
+
# Merge requests
-Merge requests allow you to exchange changes you made to source code and
-collaborate with other people on the same project.
+Merge requests allow you to visualize and collaborate on the proposed changes
+to source code that exist as commits on a given Git branch.
![Merge request view](img/merge_request.png)
@@ -511,7 +515,7 @@ seconds and the status will update automatically.
Merge Request pipeline statuses can't be retrieved when the following occurs:
-1. A Merge Requst is created
+1. A Merge Request is created
1. The Merge Request is closed
1. Changes are made in the project
1. The Merge Request is reopened
@@ -606,6 +610,9 @@ And to check out a particular merge request:
git checkout origin/merge-requests/1
```
+all the above can be done with [git-mr] script.
+
+[git-mr]: https://gitlab.com/glensc/git-mr
[products]: https://about.gitlab.com/products/ "GitLab products page"
[protected branches]: ../protected_branches.md
[ci]: ../../../ci/README.md
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index 2e9db949890..fd151a6df45 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -1,7 +1,23 @@
+---
+type: reference, concepts
+---
+
# Merge request approvals **[STARTER]**
> Introduced in [GitLab Enterprise Edition 7.12](https://about.gitlab.com/2015/06/22/gitlab-7-12-released/#merge-request-approvers-ee-only).
+Merge request approvals enable enforced code review by requiring specified people
+to approve a merge request before it can be unblocked for merging.
+
+## Use cases
+
+1. Enforcing review of all code that gets merged into a repository.
+2. Specifying code maintainers for an entire repository.
+3. Specifying reviewers for a given proposed code change.
+4. Specifying categories of reviewers, such as BE, FE, QA, DB, etc., for all proposed code changes.
+
+## Enabling the new approvals interface
+
NOTE: **Note:**
Prior to 12.0, if you are running a self-managed instance, the new interface shown on
this page will not be available unless the feature flag
@@ -21,20 +37,6 @@ sudo -u git -H bin/rails console RAILS_ENV=production
Then run `Feature.enable(:approval_rules)` to enable the feature flag.
-The documentation for the older interface can be accessed
-[here](/11.7/ee/user/project/merge_requests/merge_request_approvals.html).
-
-## Overview
-
-Merge request approvals enable enforced code review by requiring specified people to approve a merge request before it can be unblocked for merging.
-
-## Use cases
-
-1. Enforcing review of all code that gets merged into a repository.
-2. Specifying code maintainers for an entire repository.
-3. Specifying reviewers for a given proposed code change.
-4. Specifying categories of reviewers, such as BE, FE, QA, DB, etc., for all proposed code changes.
-
## Editing approvals
To edit the merge request approvals:
@@ -328,3 +330,15 @@ To filter merge requests by an individual approver, you can type (or select from
the dropdown) `approver` and select the user.
![Filter MRs by an approver](img/filter_approver_merge_requests.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/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index 1477e35dca8..c93c7a5fe08 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -1,3 +1,7 @@
+---
+type: reference, concepts
+---
+
# Merge when pipeline succeeds
When reviewing a merge request that looks ready to merge but still has one or
@@ -7,6 +11,8 @@ finish and remember to merge the request manually.
![Enable](img/merge_when_pipeline_succeeds_enable.png)
+## How it works
+
When you hit the "Merge When Pipeline Succeeds" button, the status of the merge
request will be updated to represent the impending merge. If you cannot wait
for the pipeline to succeed and want to merge immediately, this option is
@@ -29,9 +35,6 @@ changes to be reviewed.
## Only allow merge requests to be merged if the pipeline succeeds
-> **Note:**
-You need to have jobs configured to enable this feature.
-
You can prevent merge requests from being merged if their pipeline did not succeed
or if there are discussions to be resolved.
@@ -39,9 +42,21 @@ Navigate to your project's settings page and expand the **Merge requests** secti
In the **Merge checks** subsection, select the **Pipelines must succeed** check
box and hit **Save** for the changes to take effect.
-![Pipelines must succeed settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
+![Pipelines must succeed settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
From now on, every time the pipeline fails you will not be able to merge the
merge request from the UI, until you make all relevant jobs pass.
![Only allow merge if pipeline succeeds message](img/merge_when_pipeline_succeeds_only_if_succeeds_msg.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/project/merge_requests/resolve_conflicts.md b/doc/user/project/merge_requests/resolve_conflicts.md
index ccef2853e3f..6a0f2d18629 100644
--- a/doc/user/project/merge_requests/resolve_conflicts.md
+++ b/doc/user/project/merge_requests/resolve_conflicts.md
@@ -1,3 +1,7 @@
+---
+type: reference, concepts
+---
+
# Merge request conflict resolution
Merge conflicts occur when two branches have different changes that cannot be
@@ -66,3 +70,15 @@ Additionally, GitLab does not detect conflicts in renames away from a path. For
example, this will not create a conflict: on branch `a`, doing `git mv file1
file2`; on branch `b`, doing `git mv file1 file3`. Instead, both files will be
present in the branch after the merge request is merged.
+
+<!-- ## 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/project/merge_requests/revert_changes.md b/doc/user/project/merge_requests/revert_changes.md
index b9102798a49..1cbbcf45400 100644
--- a/doc/user/project/merge_requests/revert_changes.md
+++ b/doc/user/project/merge_requests/revert_changes.md
@@ -1,11 +1,13 @@
-# Reverting changes
+---
+type: reference, concepts
+---
-> [Introduced][ce-1990] in GitLab 8.5.
+# Reverting changes
-GitLab implements Git's powerful feature to [revert any commit][git-revert]
-with introducing a **Revert** button in merge requests and commit details.
+You can use Git's powerful feature to [revert any commit](https://git-scm.com/docs/git-revert "Git revert documentation")
+by clicking the **Revert** button in merge requests and commit details.
-## Reverting a Merge Request
+## Reverting a merge request
NOTE: **Note:**
The **Revert** button will only be available for merge requests
@@ -30,9 +32,9 @@ create a new merge request with the revert changes.
After the merge request has been reverted, the **Revert** button will not be
available anymore.
-## Reverting a Commit
+## Reverting a commit
-You can revert a Commit from the Commit details page:
+You can revert a commit from the commit details page:
![Revert commit](img/cherry_pick_changes_commit.png)
@@ -54,5 +56,14 @@ mainline:
git revert -m 2 7a39eb0
```
-[ce-1990]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1990 "Revert button Merge Request"
-[git-revert]: https://git-scm.com/docs/git-revert "Git revert documentation"
+<!-- ## 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/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index 4ff8ec3a7e6..39fd2588811 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -1,8 +1,14 @@
+---
+type: reference, concepts
+---
+
# Squash and merge
-> [Introduced][ee-1024] in [GitLab Starter][ee] 8.17, and in [GitLab Core][ce] [11.0][ce-18956].
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1024) in [GitLab Starter](https://about.gitlab.com/pricing/) 8.17.
+> - [Ported](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18956) to GitLab Core 11.0.
-Combine all commits of your merge request into one and retain a clean history.
+With squash and merge you can combine all your merge request's commits into one
+and retain a clean history.
## Overview
@@ -12,11 +18,11 @@ and then merges that commit using the merge method set for the project.
In other words, squashing a merge request turns a long list of commits:
-![List of commits from a merge request][mr-commits]
+![List of commits from a merge request](img/squash_mr_commits.png)
Into a single commit on merge:
-![A squashed commit followed by a merge commit][squashed-commit]
+![A squashed commit followed by a merge commit](img/squash_squashed_commit.png)
The squashed commit's commit message will be either:
@@ -44,20 +50,18 @@ all you have to do is enable squashing before you press merge to join
the commits in the merge request into a single commit.
This way, the history of your base branch remains clean with
-meaningful commit messages and is simpler to [revert] if necessary.
+meaningful commit messages and is simpler to [revert](revert_changes.md) if necessary.
## Enabling squash for a merge request
Anyone who can create or edit a merge request can choose for it to be squashed
on the merge request form:
-![Squash commits checkbox on edit form][squash-edit-form]
-
----
+![Squash commits checkbox on edit form](img/squash_edit_form.png)
This can then be overridden at the time of accepting the merge request:
-![Squash commits checkbox on accept merge request form][squash-mr-widget]
+![Squash commits checkbox on accept merge request form](img/squash_mr_widget.png)
## Commit metadata for squashed commits
@@ -69,19 +73,20 @@ The squashed commit has the following metadata:
## Squash and fast-forward merge
-When a project has the [fast-forward merge setting enabled][ff-merge], the merge
+When a project has the [fast-forward merge setting enabled](fast_forward_merge.md#enabling-fast-forward-merges), the merge
request must be able to be fast-forwarded without squashing in order to squash
it. This is because squashing is only available when accepting a merge request,
so a merge request may need to be rebased before squashing, even though
squashing can itself be considered equivalent to rebasing.
-[ee-1024]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1024
-[ce-18956]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18956
-[mr-commits]: img/squash_mr_commits.png
-[squashed-commit]: img/squash_squashed_commit.png
-[squash-edit-form]: img/squash_edit_form.png
-[squash-mr-widget]: img/squash_mr_widget.png
-[ff-merge]: fast_forward_merge.md#enabling-fast-forward-merges
-[ce]: https://about.gitlab.com/pricing/
-[ee]: https://about.gitlab.com/pricing/
-[revert]: revert_changes.md
+<!-- ## 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/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index 70bd1e60594..fbe216c3aed 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -1,13 +1,8 @@
-# Merge requests versions
+---
+type: reference, concepts
+---
-> **Notes:**
->
-> - [Introduced][ce-5467] in GitLab 8.12.
-> - Comments are disabled while viewing outdated merge versions or comparing to
-> versions other than base.
-> - Merge request versions are based on push not on commit. So, if you pushed 5
-> commits in a single push, it will be a single option in the dropdown. If you
-> pushed 5 times, that will count for 5 options.
+# Merge requests versions
Every time you push to a branch that is tied to a merge request, a new version
of merge request diff is created. When you visit a merge request that contains
@@ -16,25 +11,38 @@ request diffs.
![Merge request versions](img/versions.png)
----
+## Selecting a version
By default, the latest version of changes is shown. However, you
can select an older one from version dropdown.
![Merge request versions dropdown](img/versions_dropdown.png)
----
+Merge request versions are based on push not on commit. So, if you pushed 5
+commits in a single push, it will be a single option in the dropdown. If you
+pushed 5 times, that will count for 5 options.
You can also compare the merge request version with an older one to see what has
changed since then.
![Merge request versions compare](img/versions_compare.png)
----
+Comments are disabled while viewing outdated merge versions or comparing to
+versions other than base.
Every time you push new changes to the branch, a link to compare the last
changes appears as a system note.
![Merge request versions system note](img/versions_system_note.png)
-[ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467
+<!-- ## 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/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
index 6f33eb9a482..9cd868067bc 100644
--- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -1,4 +1,8 @@
-# "Work In Progress" Merge Requests
+---
+type: reference, concepts
+---
+
+# "Work In Progress" merge requests
If a merge request is not yet ready to be merged, perhaps due to continued development
or open discussions, you can prevent it from being accepted before it's ready by flagging
@@ -7,7 +11,7 @@ being merged, and it will stay disabled until the "WIP" flag has been removed.
![Blocked Accept Button](img/wip_blocked_accept_button.png)
-## Adding the "Work In Progress" flag to a Merge Request
+## Adding the "Work In Progress" flag to a merge request
There are several ways to flag a merge request as a Work In Progress:
@@ -21,7 +25,7 @@ There are several ways to flag a merge request as a Work In Progress:
source branch. This is not a toggle, and doing it again in another commit will have
no effect.
-## Removing the "Work In Progress" flag from a Merge Request
+## Removing the "Work In Progress" flag from a merge request
Similar to above, when a Merge Request is ready to be merged, you can remove the
"Work in Progress" flag in several ways:
@@ -37,10 +41,22 @@ Similar to above, when a Merge Request is ready to be merged, you can remove the
Must have at least Developer level permissions on the project for the button to
be visible.
-## Including/Excluding WIP Merge Requests when searching
+## Including/excluding WIP merge requests when searching
When viewing/searching the merge requests list, you can choose to include or exclude
WIP merge requests by adding a "WIP" filter in the search box, and choosing "Yes"
(to include) or "No" (to exclude).
![Filter WIP MRs](img/filter_wip_merge_requests.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/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index d36312c9b8d..c07c4099f22 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -28,10 +28,10 @@ The reasons to do it like that are:
and maximizing security.
With the new behavior, any job that is triggered by the user, is also marked
-with their permissions. When a user does a `git push` or changes files through
+with their read permissions. When a user does a `git push` or changes files through
the web UI, a new pipeline will be usually created. This pipeline will be marked
as created be the pusher (local push or via the UI) and any job created in this
-pipeline will have the permissions of the pusher.
+pipeline will have the read permissions of the pusher but not write permissions.
This allows us to make it really easy to evaluate the access for all projects
that have [Git submodules][gitsub] or are using container images that the pusher
@@ -67,9 +67,10 @@ Let's consider the following scenario:
## Job token
-A unique job token is generated for each job and it allows the user to
+A unique job token is generated for each job and provides the user read
access all projects that would be normally accessible to the user creating that
-job.
+job. The unique job token does not have any write permissions, but there
+is a [proposal to add support](https://gitlab.com/gitlab-org/gitlab-ce/issues/18106).
We try to make sure that this token doesn't leak by:
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 629b5e1fde4..002addfc043 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -16,7 +16,7 @@
> [administration/job_artifacts](../../../administration/job_artifacts.md).
Artifacts is a list of files and directories which are attached to a job
-after it completes successfully. This feature is enabled by default in all
+after it finishes. This feature is enabled by default in all
GitLab installations.
## Defining artifacts in `.gitlab-ci.yml`
@@ -36,12 +36,14 @@ pdf:
A job named `pdf` calls the `xelatex` command in order to build a pdf file from
the latex source file `mycv.tex`. We then define the `artifacts` paths which in
turn are defined with the `paths` keyword. All paths to files and directories
-are relative to the repository that was cloned during the build. These uploaded
-artifacts will be kept in GitLab for 1 week as defined by the `expire_in`
-definition. You have the option to keep the artifacts from expiring via the
-[web interface](#browsing-artifacts). If the expiry time is not defined,
-it defaults to the [instance wide
-setting](../../admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only).
+are relative to the repository that was cloned during the build.
+
+The artifacts will be uploaded when the job succeeds by default, but can be set to upload
+when the job fails, or always, if the [`artifacts:when`](../../../ci/yaml/README.md#artifactswhen)
+parameter is used. These uploaded artifacts will be kept in GitLab for 1 week as defined
+by the `expire_in` definition. You have the option to keep the artifacts from expiring
+via the [web interface](#browsing-artifacts). If the expiry time is not defined, it defaults
+to the [instance wide setting](../../admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only).
For more examples on artifacts, follow the [artifacts reference in
`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifacts).
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 8b762307ac4..16f48c462eb 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -20,6 +20,22 @@ There are two options. Using:
The default Git strategy can be overridden by the [GIT_STRATEGY variable](../../../ci/yaml/README.md#git-strategy)
in `.gitlab-ci.yml`.
+## Git shallow clone
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28919) in GitLab 12.0.
+
+NOTE: **Note**: As of GitLab 12.0, newly created projects will automaticallyl have a default
+`git depth` value of `50`.
+
+It is possible to limit the number of changes that GitLab CI/CD will fetch when cloning
+a repository. Setting a limit to `git depth` can speed up Pipelines execution. Maximum
+allowed value is `1000`.
+
+To disable shallow clone and make GitLab CI/CD fetch all branches and tags each time,
+keep the value empty or set to `0`.
+
+This value can also be [overridden by `GIT_DEPTH`](../../../ci/large_repositories/index.md#shallow-cloning) variable in `.gitlab-ci.yml` file.
+
## Timeout
Timeout defines the maximum amount of time in minutes that a job is able run.
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b1b6e7bd7b9..f8b950cb55d 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -576,6 +576,8 @@ module API
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
issue
end
+
+ expose :task_completion_status
end
class Issue < IssueBasic
@@ -724,6 +726,8 @@ module API
end
expose :squash
+
+ expose :task_completion_status
end
class MergeRequest < MergeRequestBasic
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 94b58a64d26..2a9b17ad22a 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -23,7 +23,7 @@ module API
def base_request_uri
@base_request_uri ||= URI.parse(request.url).tap do |uri|
uri.host = Gitlab.config.gitlab.host
- uri.port = nil
+ uri.port = Gitlab.config.gitlab.port
end
end
diff --git a/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
new file mode 100644
index 00000000000..0e93b2cb2fa
--- /dev/null
+++ b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # save validity time pages domain
+ class FillValidTimeForPagesDomainCertificate
+ # define PagesDomain with only needed code
+ class PagesDomain < ActiveRecord::Base
+ self.table_name = 'pages_domains'
+
+ def x509
+ return unless certificate.present?
+
+ @x509 ||= OpenSSL::X509::Certificate.new(certificate)
+ rescue OpenSSL::X509::CertificateError
+ nil
+ end
+ end
+
+ def perform(start_id, stop_id)
+ PagesDomain.where(id: start_id..stop_id).find_each do |domain|
+ if Gitlab::Database.mysql?
+ domain.update_columns(
+ certificate_valid_not_before: domain.x509&.not_before,
+ certificate_valid_not_after: domain.x509&.not_after
+ )
+ else
+ # for some reason activerecord doesn't append timezone, iso8601 forces this
+ domain.update_columns(
+ certificate_valid_not_before: domain.x509&.not_before&.iso8601,
+ certificate_valid_not_after: domain.x509&.not_after&.iso8601
+ )
+ end
+ rescue => e
+ Rails.logger.error "Failed to update pages domain certificate valid time. id: #{domain.id}, message: #{e.message}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/and.rb b/lib/gitlab/ci/pipeline/expression/lexeme/and.rb
new file mode 100644
index 00000000000..54a0e2ad9dd
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/and.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Expression
+ module Lexeme
+ class And < Lexeme::Operator
+ PATTERN = /&&/.freeze
+
+ def evaluate(variables = {})
+ @left.evaluate(variables) && @right.evaluate(variables)
+ end
+
+ def self.build(_value, behind, ahead)
+ new(behind, ahead)
+ end
+
+ def self.precedence
+ 11 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/base.rb b/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
index 70c774416f6..7ebd2e25398 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
@@ -15,10 +15,14 @@ module Gitlab
end
def self.scan(scanner)
- if scanner.scan(self::PATTERN)
+ if scanner.scan(pattern)
Expression::Token.new(scanner.matched, self)
end
end
+
+ def self.pattern
+ self::PATTERN
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
index 668e85f5b9e..62f4c14f597 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
@@ -8,11 +8,6 @@ module Gitlab
class Equals < Lexeme::Operator
PATTERN = /==/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
@left.evaluate(variables) == @right.evaluate(variables)
end
@@ -20,6 +15,10 @@ module Gitlab
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
index cd17bc4d78b..ecfab627226 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
@@ -8,21 +8,36 @@ module Gitlab
class Matches < Lexeme::Operator
PATTERN = /=~/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
text = @left.evaluate(variables)
regexp = @right.evaluate(variables)
regexp.scan(text.to_s).any?
+
+ if ci_variables_complex_expressions?
+ # return offset of first match, or nil if no matches
+ if match = regexp.scan(text.to_s).first
+ text.to_s.index(match)
+ end
+ else
+ # return true or false
+ regexp.scan(text.to_s).any?
+ end
end
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+
+ private
+
+ def ci_variables_complex_expressions?
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
index 5fcc9406cc8..8166bcd5730 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
@@ -8,11 +8,6 @@ module Gitlab
class NotEquals < Lexeme::Operator
PATTERN = /!=/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
@left.evaluate(variables) != @right.evaluate(variables)
end
@@ -20,6 +15,10 @@ module Gitlab
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
index 14544d33e25..831c27fa0ea 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
@@ -8,11 +8,6 @@ module Gitlab
class NotMatches < Lexeme::Operator
PATTERN = /\!~/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
text = @left.evaluate(variables)
regexp = @right.evaluate(variables)
@@ -23,6 +18,10 @@ module Gitlab
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb b/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
index 3ebceb92eb7..3ddab7800c8 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
@@ -6,9 +6,29 @@ module Gitlab
module Expression
module Lexeme
class Operator < Lexeme::Base
+ # This operator class is design to handle single operators that take two
+ # arguments. Expression::Parser was originally designed to read infix operators,
+ # and so the two operands are called "left" and "right" here. If we wish to
+ # implement an Operator that takes a greater or lesser number of arguments, a
+ # structural change or additional Operator superclass will likely be needed.
+
+ OperatorError = Class.new(Expression::ExpressionError)
+
+ def initialize(left, right)
+ raise OperatorError, 'Invalid left operand' unless left.respond_to? :evaluate
+ raise OperatorError, 'Invalid right operand' unless right.respond_to? :evaluate
+
+ @left = left
+ @right = right
+ end
+
def self.type
:operator
end
+
+ def self.precedence
+ raise NotImplementedError
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/or.rb b/lib/gitlab/ci/pipeline/expression/lexeme/or.rb
new file mode 100644
index 00000000000..807876f905a
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/or.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Expression
+ module Lexeme
+ class Or < Lexeme::Operator
+ PATTERN = /\|\|/.freeze
+
+ def evaluate(variables = {})
+ @left.evaluate(variables) || @right.evaluate(variables)
+ end
+
+ def self.build(_value, behind, ahead)
+ new(behind, ahead)
+ end
+
+ def self.precedence
+ 12 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
index 2b719c9c6fc..e4cf360a1c1 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
@@ -8,10 +8,11 @@ module Gitlab
require_dependency 're2'
class Pattern < Lexeme::Value
- PATTERN = %r{^/.+/[ismU]*$}.freeze
+ PATTERN = %r{^/.+/[ismU]*$}.freeze
+ NEW_PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
def initialize(regexp)
- @value = regexp
+ @value = self.class.eager_matching_with_escape_characters? ? regexp.gsub(/\\\//, '/') : regexp
unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value)
raise Lexer::SyntaxError, 'Invalid regular expression!'
@@ -24,9 +25,17 @@ module Gitlab
raise Expression::RuntimeError, 'Invalid regular expression!'
end
+ def self.pattern
+ eager_matching_with_escape_characters? ? NEW_PATTERN : PATTERN
+ end
+
def self.build(string)
new(string)
end
+
+ def self.eager_matching_with_escape_characters?
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb
index e14edfae51d..22c210ae26b 100644
--- a/lib/gitlab/ci/pipeline/expression/lexer.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexer.rb
@@ -20,6 +20,19 @@ module Gitlab
Expression::Lexeme::NotMatches
].freeze
+ NEW_LEXEMES = [
+ Expression::Lexeme::Variable,
+ Expression::Lexeme::String,
+ Expression::Lexeme::Pattern,
+ Expression::Lexeme::Null,
+ Expression::Lexeme::Equals,
+ Expression::Lexeme::Matches,
+ Expression::Lexeme::NotEquals,
+ Expression::Lexeme::NotMatches,
+ Expression::Lexeme::And,
+ Expression::Lexeme::Or
+ ].freeze
+
MAX_TOKENS = 100
def initialize(statement, max_tokens: MAX_TOKENS)
@@ -45,7 +58,7 @@ module Gitlab
return tokens if @scanner.eos?
- lexeme = LEXEMES.find do |type|
+ lexeme = available_lexemes.find do |type|
type.scan(@scanner).tap do |token|
tokens.push(token) if token.present?
end
@@ -58,6 +71,10 @@ module Gitlab
raise Lexer::SyntaxError, 'Too many tokens!'
end
+
+ def available_lexemes
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true) ? NEW_LEXEMES : LEXEMES
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb
index ed184309ab4..589bf32a4d7 100644
--- a/lib/gitlab/ci/pipeline/expression/parser.rb
+++ b/lib/gitlab/ci/pipeline/expression/parser.rb
@@ -5,17 +5,30 @@ module Gitlab
module Pipeline
module Expression
class Parser
+ ParseError = Class.new(Expression::ExpressionError)
+
def initialize(tokens)
@tokens = tokens.to_enum
@nodes = []
end
- ##
- # This produces a reverse descent parse tree.
- #
- # It currently does not support precedence of operators.
- #
def tree
+ if Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ rpn_parse_tree
+ else
+ reverse_descent_parse_tree
+ end
+ end
+
+ def self.seed(statement)
+ new(Expression::Lexer.new(statement).tokens)
+ end
+
+ private
+
+ # This produces a reverse descent parse tree.
+ # It does not support precedence of operators.
+ def reverse_descent_parse_tree
while token = @tokens.next
case token.type
when :operator
@@ -32,8 +45,51 @@ module Gitlab
@nodes.last || Lexeme::Null.new
end
- def self.seed(statement)
- new(Expression::Lexer.new(statement).tokens)
+ def rpn_parse_tree
+ results = []
+
+ tokens_rpn.each do |token|
+ case token.type
+ when :value
+ results.push(token.build)
+ when :operator
+ right_operand = results.pop
+ left_operand = results.pop
+
+ token.build(left_operand, right_operand).tap do |res|
+ results.push(res)
+ end
+ else
+ raise ParseError, 'Unprocessable token found in parse tree'
+ end
+ end
+
+ raise ParseError, 'Unreachable nodes in parse tree' if results.count > 1
+ raise ParseError, 'Empty parse tree' if results.count < 1
+
+ results.pop
+ end
+
+ # Parse the expression into Reverse Polish Notation
+ # (See: Shunting-yard algorithm)
+ def tokens_rpn
+ output = []
+ operators = []
+
+ @tokens.each do |token|
+ case token.type
+ when :value
+ output.push(token)
+ when :operator
+ if operators.any? && token.lexeme.precedence >= operators.last.lexeme.precedence
+ output.push(operators.pop)
+ end
+
+ operators.push(token)
+ end
+ end
+
+ output.concat(operators.reverse)
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb
index ab5ae9caeea..0e81e1bd34c 100644
--- a/lib/gitlab/ci/pipeline/expression/statement.rb
+++ b/lib/gitlab/ci/pipeline/expression/statement.rb
@@ -7,27 +7,6 @@ module Gitlab
class Statement
StatementError = Class.new(Expression::ExpressionError)
- GRAMMAR = [
- # presence matchers
- %w[variable],
-
- # positive matchers
- %w[variable equals string],
- %w[variable equals variable],
- %w[variable equals null],
- %w[string equals variable],
- %w[null equals variable],
- %w[variable matches pattern],
-
- # negative matchers
- %w[variable notequals string],
- %w[variable notequals variable],
- %w[variable notequals null],
- %w[string notequals variable],
- %w[null notequals variable],
- %w[variable notmatches pattern]
- ].freeze
-
def initialize(statement, variables = {})
@lexer = Expression::Lexer.new(statement)
@variables = variables.with_indifferent_access
@@ -36,10 +15,6 @@ module Gitlab
def parse_tree
raise StatementError if @lexer.lexemes.empty?
- unless GRAMMAR.find { |syntax| syntax == @lexer.lexemes }
- raise StatementError, 'Unknown pipeline expression!'
- end
-
Expression::Parser.new(@lexer.tokens).tree
end
@@ -54,6 +29,7 @@ module Gitlab
end
def valid?
+ evaluate
parse_tree.is_a?(Lexeme::Base)
rescue Expression::ExpressionError
false
diff --git a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
index 0fb7c57ab72..3f46bb89e94 100644
--- a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
@@ -8,25 +8,13 @@ stages:
- deploy
.serverless:build:image:
- variables:
- DOCKERFILE: "Dockerfile"
stage: build
- image:
- name: gcr.io/kaniko-project/executor:debug
- entrypoint: [""]
- only:
- refs:
- - master
- script:
- - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/$DOCKERFILE --destination $CI_REGISTRY_IMAGE
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ script: /usr/bin/gitlabktl app build
.serverless:deploy:image:
stage: deploy
- image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5
- only:
- refs:
- - master
+ image: gcr.io/triggermesh/tm@sha256:3cfdd470a66b741004fb02354319d79f1598c70117ce79978d2e07e192bfb336 # v0.0.11
environment: development
script:
- echo "$CI_REGISTRY_IMAGE"
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index 7effb802678..7a0fb419f8e 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -72,7 +72,8 @@ module Gitlab
CATEGORY_LABELS = {
docs: "~Documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
none: "",
- qa: "~QA"
+ qa: "~QA",
+ test: "~test for `spec/features/*`"
}.freeze
CATEGORIES = {
%r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
@@ -104,6 +105,7 @@ module Gitlab
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
+ %r{\A(ee/)?spec/features/} => :test,
%r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
%r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
%r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index c4e66da8ed1..b44f134f2c1 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -3,11 +3,12 @@
module Gitlab
module Danger
class Teammate
- attr_reader :name, :username, :projects
+ attr_reader :name, :username, :role, :projects
def initialize(options = {})
@username = options['username']
@name = options['name'] || @username
+ @role = options['role']
@projects = options['projects']
end
@@ -20,20 +21,32 @@ module Gitlab
end
# Traintainers also count as reviewers
- def reviewer?(project, category)
- capabilities(project).include?("reviewer #{category}") || traintainer?(project, category)
+ def reviewer?(project, category, labels)
+ has_capability?(project, category, :reviewer, labels) ||
+ traintainer?(project, category, labels)
end
- def traintainer?(project, category)
- capabilities(project).include?("trainee_maintainer #{category}")
+ def traintainer?(project, category, labels)
+ has_capability?(project, category, :trainee_maintainer, labels)
end
- def maintainer?(project, category)
- capabilities(project).include?("maintainer #{category}")
+ def maintainer?(project, category, labels)
+ has_capability?(project, category, :maintainer, labels)
end
private
+ def has_capability?(project, category, kind, labels)
+ case category
+ when :test
+ area = role[/Test Automation Engineer, (\w+)/, 1]
+
+ area && labels.any?(area) if kind == :reviewer
+ else
+ capabilities(project).include?("#{kind} #{category}")
+ end
+ end
+
def capabilities(project)
Array(projects.fetch(project, []))
end
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
index c8c03d5d001..6e17ffaf6ff 100644
--- a/lib/gitlab/diff/suggestions_parser.rb
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -10,10 +10,12 @@ module Gitlab
# Returns an array of Gitlab::Diff::Suggestion which represents each
# suggestion in the given text.
#
- def parse(text, position:, project:)
+ def parse(text, position:, project:, supports_suggestion: true)
return [] unless position.complete?
- html = Banzai.render(text, project: nil, no_original_data: true)
+ html = Banzai.render(text, project: nil,
+ no_original_data: true,
+ suggestions_filter_enabled: supports_suggestion)
doc = Nokogiri::HTML(html)
suggestion_nodes = doc.search('pre.suggestion')
diff --git a/lib/gitlab/lets_encrypt/challenge.rb b/lib/gitlab/lets_encrypt/challenge.rb
index 6a7f5e965c5..136268c974b 100644
--- a/lib/gitlab/lets_encrypt/challenge.rb
+++ b/lib/gitlab/lets_encrypt/challenge.rb
@@ -7,7 +7,7 @@ module Gitlab
@acme_challenge = acme_challenge
end
- delegate :url, :token, :file_content, :status, :request_validation, to: :acme_challenge
+ delegate :token, :file_content, :status, :request_validation, to: :acme_challenge
private
diff --git a/lib/gitlab/lets_encrypt/order.rb b/lib/gitlab/lets_encrypt/order.rb
index 5109b5e9843..9c2365f98a8 100644
--- a/lib/gitlab/lets_encrypt/order.rb
+++ b/lib/gitlab/lets_encrypt/order.rb
@@ -13,7 +13,16 @@ module Gitlab
::Gitlab::LetsEncrypt::Challenge.new(challenge)
end
- delegate :url, :status, to: :acme_order
+ def request_certificate(domain:, private_key:)
+ csr = ::Acme::Client::CertificateRequest.new(
+ private_key: OpenSSL::PKey.read(private_key),
+ subject: { common_name: domain }
+ )
+
+ acme_order.finalize(csr: csr)
+ end
+
+ delegate :url, :status, :expires, :certificate, to: :acme_order
private
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 4d9c43f37e7..17eacbd21d8 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -77,10 +77,10 @@ module Gitlab
end
def worker_label
+ return { worker: 'sidekiq' } if Sidekiq.server?
return {} unless defined?(Unicorn::Worker)
worker_no = ::Prometheus::Client::Support::Unicorn.worker_id
-
if worker_no
{ worker: worker_no }
else
diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb
index 2a2083ebae0..83204fa5d18 100644
--- a/lib/gitlab/omniauth_initializer.rb
+++ b/lib/gitlab/omniauth_initializer.rb
@@ -63,6 +63,12 @@ module Gitlab
{ remote_sign_out_handler: authentiq_signout_handler }
when 'shibboleth'
{ fail_with_empty_uid: true }
+ when 'openid_connect'
+ # If a name argument is omitted, OmniAuth will expect that the
+ # matching route is /auth/users/openidconnect instead of
+ # /auth/users/openid_connect because of
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/62208#note_178780341.
+ { name: 'openid_connect' }
else
{}
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 7c1e6b1baff..3daa03d01d6 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -85,6 +85,10 @@ module Gitlab
UsersFinder.new(current_user, search: query).execute
end
+ def display_options(_scope)
+ {}
+ end
+
private
def projects
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index 61fcb562106..0d3e78c0a66 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -43,7 +43,13 @@ module Gitlab
config[:'gitaly-ruby'] = { dir: File.join(gitaly_dir, 'ruby') } if gitaly_ruby
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
config[:bin_dir] = Gitlab.config.gitaly.client_path
- config[:git] = { catfile_cache_size: 5 }
+
+ if Rails.env.test?
+ # Compared to production, tests run in constrained environments. This
+ # number is meant to grow with the number of concurrent rails requests /
+ # sidekiq jobs, and concurrency will be low anyway in test.
+ config[:git] = { catfile_cache_size: 5 }
+ end
TomlRB.dump(config)
end
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
index 2235a6ba194..91a52144dd2 100644
--- a/lib/tasks/gettext.rake
+++ b/lib/tasks/gettext.rake
@@ -19,6 +19,7 @@ namespace :gettext do
Rake::Task['gettext:po_to_json'].invoke
end
+ desc 'Regenerate gitlab.pot file'
task :regenerate do
pot_file = 'locale/gitlab.pot'
# Remove all translated files, this speeds up finding
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 39e18147311..2845daf51e3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -427,7 +427,7 @@ msgstr ""
msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
msgstr ""
-msgid "A member of GitLab's abuse team will review your report as soon as possible."
+msgid "A member of the abuse team will review your report as soon as possible."
msgstr ""
msgid "A new branch will be created in your fork and a new merge request will be started."
@@ -1485,9 +1485,6 @@ msgstr ""
msgid "Begin with the selected commit"
msgstr ""
-msgid "Behavior"
-msgstr ""
-
msgid "Below are examples of regex for existing tools:"
msgstr ""
@@ -1911,9 +1908,6 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose between fixed (max. 1280px) and fluid (100%%) application layout."
-msgstr ""
-
msgid "Choose file…"
msgstr ""
@@ -1923,9 +1917,6 @@ msgstr ""
msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose what content you want to see on a project’s overview page."
-msgstr ""
-
msgid "Choose your merge method, options, checks, and set up a default merge request description template."
msgstr ""
@@ -2136,7 +2127,7 @@ msgstr ""
msgid "ClusterIntegration|%{title} uninstalled successfully."
msgstr ""
-msgid "ClusterIntegration|%{title} upgraded successfully."
+msgid "ClusterIntegration|%{title} updated successfully."
msgstr ""
msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges."
@@ -2331,15 +2322,6 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Install"
-msgstr ""
-
-msgid "ClusterIntegration|Installed"
-msgstr ""
-
-msgid "ClusterIntegration|Installing"
-msgstr ""
-
msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
msgstr ""
@@ -2427,9 +2409,6 @@ msgstr ""
msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Manage"
-msgstr ""
-
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
@@ -2496,9 +2475,6 @@ msgstr ""
msgid "ClusterIntegration|Request to begin uninstalling failed"
msgstr ""
-msgid "ClusterIntegration|Retry update"
-msgstr ""
-
msgid "ClusterIntegration|Save changes"
msgstr ""
@@ -2598,21 +2574,6 @@ msgstr ""
msgid "ClusterIntegration|Update failed. Please check the logs and try again."
msgstr ""
-msgid "ClusterIntegration|Updating"
-msgstr ""
-
-msgid "ClusterIntegration|Upgrade"
-msgstr ""
-
-msgid "ClusterIntegration|Upgrade failed"
-msgstr ""
-
-msgid "ClusterIntegration|Upgraded"
-msgstr ""
-
-msgid "ClusterIntegration|Upgrading"
-msgstr ""
-
msgid "ClusterIntegration|Validating project billing status"
msgstr ""
@@ -3197,9 +3158,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize the appearance of the application header and navigation sidebar."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3287,9 +3245,6 @@ msgstr ""
msgid "Default classification label"
msgstr ""
-msgid "Default dashboard"
-msgstr ""
-
msgid "Default first day of the week"
msgstr ""
@@ -4698,6 +4653,9 @@ msgstr ""
msgid "Git revision"
msgstr ""
+msgid "Git shallow clone"
+msgstr ""
+
msgid "Git strategy for pipelines"
msgstr ""
@@ -5339,6 +5297,9 @@ msgstr ""
msgid "Insert suggestion"
msgstr ""
+msgid "Install"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5348,6 +5309,12 @@ msgstr ""
msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
msgstr ""
+msgid "Installed"
+msgstr ""
+
+msgid "Installing"
+msgstr ""
+
msgid "Instance Statistics"
msgstr ""
@@ -5390,6 +5357,9 @@ msgstr ""
msgid "Invalid Login or password"
msgstr ""
+msgid "Invalid date"
+msgstr ""
+
msgid "Invalid feature"
msgstr ""
@@ -5791,9 +5761,6 @@ msgstr ""
msgid "Latest pipeline for this branch"
msgstr ""
-msgid "Layout width"
-msgstr ""
-
msgid "Lead"
msgstr ""
@@ -5940,6 +5907,9 @@ msgstr ""
msgid "Makes this issue confidential"
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -6994,6 +6964,9 @@ msgstr ""
msgid "People without permission will never get a notification and won't be able to comment."
msgstr ""
+msgid "People without permission will never get a notification."
+msgstr ""
+
msgid "Perform advanced options such as changing path, transferring, or removing the group."
msgstr ""
@@ -7315,7 +7288,7 @@ msgstr ""
msgid "Please try again"
msgstr ""
-msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
+msgid "Please use this form to report to the admin users who create spam issues, comments or behave inappropriately."
msgstr ""
msgid "Please wait a moment, this page will automatically refresh when ready."
@@ -7330,21 +7303,51 @@ msgstr ""
msgid "Preferences saved."
msgstr ""
+msgid "Preferences|Behavior"
+msgstr ""
+
+msgid "Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout."
+msgstr ""
+
+msgid "Preferences|Choose what content you want to see on a project’s overview page."
+msgstr ""
+
+msgid "Preferences|Customize the appearance of the application header and navigation sidebar."
+msgstr ""
+
+msgid "Preferences|Default dashboard"
+msgstr ""
+
msgid "Preferences|Display time in 24-hour format"
msgstr ""
msgid "Preferences|For example: 30 mins ago."
msgstr ""
+msgid "Preferences|Layout width"
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|Project overview content"
+msgstr ""
+
+msgid "Preferences|Syntax highlighting theme"
+msgstr ""
+
msgid "Preferences|These settings will update how dates and times are displayed for you."
msgstr ""
msgid "Preferences|This feature is experimental and translations are not complete yet"
msgstr ""
+msgid "Preferences|This setting allows you to customize the appearance of the syntax."
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views."
+msgstr ""
+
msgid "Preferences|Time display"
msgstr ""
@@ -7780,9 +7783,6 @@ msgstr ""
msgid "Project name"
msgstr ""
-msgid "Project overview content"
-msgstr ""
-
msgid "Project slug"
msgstr ""
@@ -8385,7 +8385,7 @@ msgstr ""
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr ""
-msgid "Report abuse to GitLab"
+msgid "Report abuse to admin"
msgstr ""
msgid "Reporting"
@@ -8532,6 +8532,9 @@ msgstr ""
msgid "Retry this job in order to create the necessary resources."
msgstr ""
+msgid "Retry update"
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -9130,6 +9133,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "SignUp|Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
msgid "Signed in"
msgstr ""
@@ -9679,9 +9688,6 @@ msgstr ""
msgid "Switch to the source to copy it to the clipboard"
msgstr ""
-msgid "Syntax highlighting theme"
-msgstr ""
-
msgid "System Hooks"
msgstr ""
@@ -9972,6 +9978,9 @@ msgstr ""
msgid "The name %{entryName} is already taken in this directory."
msgstr ""
+msgid "The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time."
+msgstr ""
+
msgid "The number of times an upload record could not find its file"
msgstr ""
@@ -10182,6 +10191,9 @@ msgstr ""
msgid "Third party offers"
msgstr ""
+msgid "This %{issuableDisplayName} is locked. Only project members can comment."
+msgstr ""
+
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
@@ -10278,9 +10290,6 @@ msgstr ""
msgid "This issue is confidential"
msgstr ""
-msgid "This issue is confidential and locked."
-msgstr ""
-
msgid "This issue is locked."
msgstr ""
@@ -10392,12 +10401,6 @@ msgstr ""
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
-msgid "This setting allows you to customize the appearance of the syntax."
-msgstr ""
-
-msgid "This setting allows you to customize the behavior of the system layout and default views."
-msgstr ""
-
msgid "This setting can be overridden in each project."
msgstr ""
@@ -11002,6 +11005,9 @@ msgstr ""
msgid "UpdateProject|Project could not be updated!"
msgstr ""
+msgid "Updated"
+msgstr ""
+
msgid "Updating"
msgstr ""
@@ -11931,9 +11937,6 @@ msgstr ""
msgid "Your comment could not be updated! Please check your network connection and try again."
msgstr ""
-msgid "Your comment will not be visible to the public."
-msgstr ""
-
msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
msgstr ""
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index 4096f57b7db..c81e13e9b91 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -8,8 +8,8 @@ module QA
class Show < Page::Base
view 'app/assets/javascripts/clusters/components/application_row.vue' do
element :application_row, 'js-cluster-application-row-${this.id}' # rubocop:disable QA/ElementWithPattern
- element :install_button, "s__('ClusterIntegration|Install')" # rubocop:disable QA/ElementWithPattern
- element :installed_button, "s__('ClusterIntegration|Installed')" # rubocop:disable QA/ElementWithPattern
+ element :install_button, "__('Install')" # rubocop:disable QA/ElementWithPattern
+ element :installed_button, "__('Installed')" # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/clusters/components/applications.vue' do
diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb
index cad89ebb0bb..e98880ce195 100644
--- a/qa/qa/resource/repository/project_push.rb
+++ b/qa/qa/resource/repository/project_push.rb
@@ -32,8 +32,8 @@ module QA
def fabricate!
super
- project.visit!
project.wait_for_push @commit_message if @wait_for_push
+ project.visit!
end
end
end
diff --git a/spec/controllers/acme_challenges_controller_spec.rb b/spec/controllers/acme_challenges_controller_spec.rb
new file mode 100644
index 00000000000..cee06bed27b
--- /dev/null
+++ b/spec/controllers/acme_challenges_controller_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AcmeChallengesController do
+ describe '#show' do
+ let!(:acme_order) { create(:pages_domain_acme_order) }
+
+ def make_request(domain, token)
+ get(:show, params: { domain: domain, token: token })
+ end
+
+ before do
+ make_request(domain, token)
+ end
+
+ context 'with right domain and token' do
+ let(:domain) { acme_order.pages_domain.domain }
+ let(:token) { acme_order.challenge_token }
+
+ it 'renders acme challenge file content' do
+ expect(response.body).to eq(acme_order.challenge_file_content)
+ end
+ end
+
+ context 'when domain is invalid' do
+ let(:domain) { 'somewrongdomain.com' }
+ let(:token) { acme_order.challenge_token }
+
+ it 'renders not found' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when token is invalid' do
+ let(:domain) { acme_order.pages_domain.domain }
+ let(:token) { 'wrongtoken' }
+
+ it 'renders not found' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 5ecd1b6b7c8..40669ec5451 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -691,4 +691,38 @@ describe ApplicationController do
end
end
end
+
+ context 'Gitlab::Session' do
+ controller(described_class) do
+ prepend_before_action do
+ authenticate_sessionless_user!(:rss)
+ end
+
+ def index
+ if Gitlab::Session.current
+ head :created
+ else
+ head :not_found
+ end
+ end
+ end
+
+ it 'is set on web requests' do
+ sign_in(user)
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ context 'with sessionless user' do
+ it 'is not set' do
+ personal_access_token = create(:personal_access_token, user: user)
+
+ get :index, format: :atom, params: { private_token: personal_access_token.token }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index de1b9dc0bf3..d463619ad0b 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -39,7 +39,7 @@ describe Projects::AvatarsController do
end
context 'when the avatar is stored in lfs' do
- it_behaves_like 'repository lfs file load' do
+ it_behaves_like 'a controller that can serve LFS files' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "files/lfs/#{filename}" }
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index d5eea5b0439..9699f2952f2 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -433,20 +433,6 @@ describe Projects::EnvironmentsController do
end
context 'when only one time param is provided' do
- context 'when :metrics_time_window feature flag is disabled' do
- before do
- stub_feature_flags(metrics_time_window: false)
- expect(environment).to receive(:additional_metrics).with(no_args).and_return(nil)
- end
-
- it 'returns a time-window agnostic response' do
- additional_metrics(start: '1552647300.651094')
-
- expect(response).to have_gitlab_http_status(204)
- expect(json_response).to eq({})
- end
- end
-
it 'raises an error when start is missing' do
expect { additional_metrics(end: '1552647300.651094') }
.to raise_error(ActionController::ParameterMissing)
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 3a89d8ce032..97acd47b4da 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -42,7 +42,7 @@ describe Projects::RawController do
end
end
- it_behaves_like 'repository lfs file load' do
+ it_behaves_like 'a controller that can serve LFS files' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "be93687/files/lfs/#{filename}" }
end
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index b91a4df40a5..117b9cf7915 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -200,6 +200,21 @@ describe Projects::Settings::CiCdController do
expect(response).to redirect_to(namespace_project_settings_ci_cd_path)
end
end
+
+ context 'when default_git_depth is not specified' do
+ let(:params) { { ci_cd_settings_attributes: { default_git_depth: 10 } } }
+
+ before do
+ project.ci_cd_settings.update!(default_git_depth: nil)
+ end
+
+ it 'set specified git depth' do
+ subject
+
+ project.reload
+ expect(project.default_git_depth).to eq(10)
+ end
+ end
end
end
end
diff --git a/spec/factories/pages_domain_acme_orders.rb b/spec/factories/pages_domain_acme_orders.rb
new file mode 100644
index 00000000000..7f9ee1c8f9c
--- /dev/null
+++ b/spec/factories/pages_domain_acme_orders.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :pages_domain_acme_order do
+ pages_domain
+ url { 'https://example.com/' }
+ expires_at { 1.day.from_now }
+ challenge_token { 'challenge_token' }
+ challenge_file_content { 'filecontent' }
+
+ private_key { OpenSSL::PKey::RSA.new(4096).to_pem }
+
+ trait :expired do
+ expires_at { 1.day.ago }
+ end
+ end
+end
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index 83cd686818c..f6c498f7a4c 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Appearance' do
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index dfa1c92ea49..d523e2992db 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Dashboard Issues Feed" do
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index e1bc4eca619..59230d6891a 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group merge requests page' do
diff --git a/spec/features/instance_statistics/conversational_development_index_spec.rb b/spec/features/instance_statistics/conversational_development_index_spec.rb
index d8be554d734..713cd944f8c 100644
--- a/spec/features/instance_statistics/conversational_development_index_spec.rb
+++ b/spec/features/instance_statistics/conversational_development_index_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Conversational Development Index' do
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index bc0ec58bd24..5ee9425c491 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issues' do
diff --git a/spec/features/merge_request/user_merges_merge_request_spec.rb b/spec/features/merge_request/user_merges_merge_request_spec.rb
index 6539e6e9208..da15a4bda4b 100644
--- a/spec/features/merge_request/user_merges_merge_request_spec.rb
+++ b/spec/features/merge_request/user_merges_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User merges a merge request", :js do
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index 76abc640077..95685a3c7ff 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project variables', :js do
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index a85e7333ba8..ce382c19fc1 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Clusters', :js do
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index d0878c4088a..03562bd382e 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -936,8 +936,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
find('.js-cancel-job').click
end
- it 'loads the page and shows no controls' do
- expect(page).not_to have_content 'Retry'
+ it 'loads the page and shows all needed controls' do
+ expect(page).to have_content 'Retry'
end
end
end
@@ -946,7 +946,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context "Job from project", :js do
before do
job.run!
- job.drop!(:script_failure)
+ job.cancel!
visit project_job_path(project, job)
wait_for_requests
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index b54ea929978..033e1afe866 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -63,6 +63,36 @@ describe 'New project' do
end
end
end
+
+ context 'when group visibility is private but default is internal' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ visit new_project_path(namespace_id: group.id)
+
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
+ end
+ end
+
+ context 'when group visibility is public but user requests private' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
+ end
+ end
end
context 'Readme selector' do
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 506aa867490..1de153db41c 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipeline', :js do
@@ -115,11 +117,11 @@ describe 'Pipeline', :js do
end
end
- it 'cancels the running build and does not show retry button' do
+ it 'cancels the running build and shows retry button' do
find('#ci-badge-deploy .ci-action-icon-container').click
page.within('#ci-badge-deploy') do
- expect(page).not_to have_css('.js-icon-retry')
+ expect(page).to have_css('.js-icon-retry')
end
end
end
@@ -133,11 +135,11 @@ describe 'Pipeline', :js do
end
end
- it 'cancels the preparing build and does not show retry button' do
+ it 'cancels the preparing build and shows retry button' do
find('#ci-badge-deploy .ci-action-icon-container').click
page.within('#ci-badge-deploy') do
- expect(page).not_to have_css('.js-icon-retry')
+ expect(page).to have_css('.js-icon-retry')
end
end
end
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
index 28d52f25f56..0739726f52c 100644
--- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -51,7 +51,7 @@ describe 'Projects > Settings > User manages merge request settings' do
end
it 'shows the Merge Requests settings that do not depend on Builds feature' do
- expect(page).not_to have_content 'Pipelines must succeed'
+ expect(page).to have_content 'Pipelines must succeed'
expect(page).to have_content 'All discussions must be resolved'
within('.sharing-permissions-form') do
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 27f6ed56283..b5112758475 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project' do
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index a198e65046f..044a47567be 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Profile access" do
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index 957c3cfc583..1a9caf0ffbb 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -25,6 +25,13 @@ describe 'Signup' do
expect(find('.username')).not_to have_css '.gl-field-error-outline'
end
+ it 'does not show an error border if the username length is not longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 255
+ wait_for_requests
+
+ expect(find('.username')).not_to have_css '.gl-field-error-outline'
+ end
+
it 'shows an error border if the username already exists' do
existing_user = create(:user)
@@ -41,6 +48,20 @@ describe 'Signup' do
expect(find('.username')).to have_css '.gl-field-error-outline'
end
+ it 'shows an error border if the username is longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 256
+ wait_for_requests
+
+ expect(find('.username')).to have_css '.gl-field-error-outline'
+ end
+
+ it 'shows an error message if the username is longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 256
+ wait_for_requests
+
+ expect(page).to have_content("Username is too long (maximum is 255 characters).")
+ end
+
it 'shows an error message on submit if the username contains special characters' do
fill_in 'new_user_username', with: 'new$user!username'
wait_for_requests
@@ -67,14 +88,35 @@ describe 'Signup' do
before do
visit root_path
click_link 'Register'
- simulate_input('#new_user_name', 'Ehsan 🦋')
+ end
+
+ it 'does not show an error border if the user\'s fullname length is not longer than 128 characters' do
+ fill_in 'new_user_name', with: 'u' * 128
+
+ expect(find('.name')).not_to have_css '.gl-field-error-outline'
end
it 'shows an error border if the user\'s fullname contains an emoji' do
+ simulate_input('#new_user_name', 'Ehsan 🦋')
+
+ expect(find('.name')).to have_css '.gl-field-error-outline'
+ end
+
+ it 'shows an error border if the user\'s fullname is longer than 128 characters' do
+ fill_in 'new_user_name', with: 'n' * 129
+
expect(find('.name')).to have_css '.gl-field-error-outline'
end
+ it 'shows an error message if the user\'s fullname is longer than 128 characters' do
+ fill_in 'new_user_name', with: 'n' * 129
+
+ expect(page).to have_content("Name is too long (maximum is 128 characters).")
+ end
+
it 'shows an error message if the username contains emojis' do
+ simulate_input('#new_user_name', 'Ehsan 🦋')
+
expect(page).to have_content("Invalid input, please avoid emojis")
end
end
diff --git a/spec/frontend/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js
index 3257a3fb8a3..4dd27e94d97 100644
--- a/spec/frontend/boards/modal_store_spec.js
+++ b/spec/frontend/boards/modal_store_spec.js
@@ -1,7 +1,7 @@
/* global ListIssue */
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import Store from '~/boards/stores/modal_store';
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index 66b22fa2681..6de06a9e2d5 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -1,5 +1,10 @@
import Clusters from '~/clusters/clusters_bundle';
-import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX, APPLICATIONS } from '~/clusters/constants';
+import {
+ APPLICATION_STATUS,
+ INGRESS_DOMAIN_SUFFIX,
+ APPLICATIONS,
+ RUNNER,
+} from '~/clusters/constants';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { loadHTMLFixture } from 'helpers/fixtures';
@@ -353,4 +358,30 @@ describe('Clusters', () => {
});
});
});
+
+ describe('updateApplication', () => {
+ const params = { version: '1.0.0' };
+ let storeUpdateApplication;
+ let installApplication;
+
+ beforeEach(() => {
+ storeUpdateApplication = jest.spyOn(cluster.store, 'updateApplication');
+ installApplication = jest.spyOn(cluster.service, 'installApplication');
+
+ cluster.updateApplication({ id: RUNNER, params });
+ });
+
+ afterEach(() => {
+ storeUpdateApplication.mockRestore();
+ installApplication.mockRestore();
+ });
+
+ it('calls store updateApplication method', () => {
+ expect(storeUpdateApplication).toHaveBeenCalledWith(RUNNER);
+ });
+
+ it('sends installApplication request', () => {
+ expect(installApplication).toHaveBeenCalledWith(RUNNER, params);
+ });
+ });
});
diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js
index 7c781b72355..9f127ccb690 100644
--- a/spec/frontend/clusters/components/application_row_spec.js
+++ b/spec/frontend/clusters/components/application_row_spec.js
@@ -245,26 +245,26 @@ describe('Application Row', () => {
});
});
- describe('Upgrade button', () => {
+ describe('Update button', () => {
it('has indeterminate state on page load', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: null,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- expect(upgradeBtn).toBe(null);
+ expect(updateBtn).toBe(null);
});
- it('has enabled "Upgrade" when "upgradeAvailable" is true', () => {
+ it('has enabled "Update" when "updateAvailable" is true', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- upgradeAvailable: true,
+ updateAvailable: true,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- expect(upgradeBtn).not.toBe(null);
- expect(upgradeBtn.innerHTML).toContain('Upgrade');
+ expect(updateBtn).not.toBe(null);
+ expect(updateBtn.innerHTML).toContain('Update');
});
it('has enabled "Retry update" when update process fails', () => {
@@ -273,10 +273,10 @@ describe('Application Row', () => {
status: APPLICATION_STATUS.INSTALLED,
updateFailed: true,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- expect(upgradeBtn).not.toBe(null);
- expect(upgradeBtn.innerHTML).toContain('Retry update');
+ expect(updateBtn).not.toBe(null);
+ expect(updateBtn.innerHTML).toContain('Retry update');
});
it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => {
@@ -284,53 +284,51 @@ describe('Application Row', () => {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.UPDATING,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- expect(upgradeBtn).not.toBe(null);
- expect(vm.isUpgrading).toBe(true);
- expect(upgradeBtn.innerHTML).toContain('Updating');
+ expect(updateBtn).not.toBe(null);
+ expect(vm.isUpdating).toBe(true);
+ expect(updateBtn.innerHTML).toContain('Updating');
});
- it('clicking upgrade button emits event', () => {
+ it('clicking update button emits event', () => {
jest.spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.INSTALLED,
- upgradeAvailable: true,
+ updateAvailable: true,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- upgradeBtn.click();
+ updateBtn.click();
- expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', {
+ expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
id: DEFAULT_APPLICATION_STATE.id,
params: {},
});
});
- it('clicking disabled upgrade button emits nothing', () => {
+ it('clicking disabled update button emits nothing', () => {
jest.spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.UPDATING,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- upgradeBtn.click();
+ updateBtn.click();
expect(eventHub.$emit).not.toHaveBeenCalled();
});
- it('displays an error message if application upgrade failed', () => {
+ it('displays an error message if application update failed', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
title: 'GitLab Runner',
status: APPLICATION_STATUS.INSTALLED,
updateFailed: true,
});
- const failureMessage = vm.$el.querySelector(
- '.js-cluster-application-upgrade-failure-message',
- );
+ const failureMessage = vm.$el.querySelector('.js-cluster-application-update-details');
expect(failureMessage).not.toBe(null);
expect(failureMessage.innerHTML).toContain(
@@ -338,7 +336,7 @@ describe('Application Row', () => {
);
});
- it('displays a success toast message if application upgrade was successful', () => {
+ it('displays a success toast message if application update was successful', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
title: 'GitLab Runner',
@@ -349,13 +347,13 @@ describe('Application Row', () => {
vm.updateSuccessful = true;
return vm.$nextTick(() => {
- expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner upgraded successfully.');
+ expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner updated successfully.');
});
});
});
describe('Version', () => {
- it('displays a version number if application has been upgraded', () => {
+ it('displays a version number if application has been updated', () => {
const version = '0.1.45';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
@@ -363,15 +361,15 @@ describe('Application Row', () => {
updateSuccessful: true,
version,
});
- const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+ const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
- expect(upgradeDetails.innerHTML).toContain('Upgraded');
+ expect(updateDetails.innerHTML).toContain('Updated');
expect(versionEl).not.toBe(null);
expect(versionEl.innerHTML).toContain(version);
});
- it('contains a link to the chart repo if application has been upgraded', () => {
+ 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';
vm = mountComponent(ApplicationRow, {
@@ -381,13 +379,13 @@ describe('Application Row', () => {
chartRepo,
version,
});
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
expect(versionEl.href).toEqual(chartRepo);
expect(versionEl.target).toEqual('_blank');
});
- it('does not display a version number if application upgrade failed', () => {
+ it('does not display a version number if application update failed', () => {
const version = '0.1.45';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
@@ -395,10 +393,10 @@ describe('Application Row', () => {
updateFailed: true,
version,
});
- const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+ const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
- expect(upgradeDetails.innerHTML).toContain('failed');
+ expect(updateDetails.innerHTML).toContain('failed');
expect(versionEl).toBe(null);
});
});
diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js
index e057e2ac955..c146ef79be7 100644
--- a/spec/frontend/clusters/services/application_state_machine_spec.js
+++ b/spec/frontend/clusters/services/application_state_machine_spec.js
@@ -127,7 +127,7 @@ describe('applicationStateMachine', () => {
describe(`current state is ${UPDATING}`, () => {
it.each`
expectedState | event | effects
- ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true, updateAcknowledged: false }}
+ ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true }}
${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
`(`transitions to $expectedState on $event event and applies $effects`, data => {
const { expectedState, event, effects } = data;
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
index 0d129349799..f2cc413512d 100644
--- a/spec/frontend/clusters/stores/clusters_store_spec.js
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -85,11 +85,10 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[2].status_reason,
requestReason: null,
version: mockResponseData.applications[2].version,
- upgradeAvailable: mockResponseData.applications[2].update_available,
+ updateAvailable: mockResponseData.applications[2].update_available,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
installed: false,
installFailed: false,
- updateAcknowledged: true,
updateFailed: false,
updateSuccessful: false,
uninstallable: false,
diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js
index e74598ae20a..702ef0be5aa 100644
--- a/spec/frontend/helpers/timeout.js
+++ b/spec/frontend/helpers/timeout.js
@@ -5,7 +5,13 @@ const IS_DEBUGGING = process.execArgv.join(' ').includes('--inspect-brk');
let testTimeoutNS;
export const setTestTimeout = newTimeoutMS => {
- testTimeoutNS = newTimeoutMS * NS_PER_MS;
+ const newTimeoutNS = newTimeoutMS * NS_PER_MS;
+ // never accept a smaller timeout than the default
+ if (newTimeoutNS < testTimeoutNS) {
+ return;
+ }
+
+ testTimeoutNS = newTimeoutNS;
jest.setTimeout(newTimeoutMS);
};
@@ -13,7 +19,13 @@ export const setTestTimeout = newTimeoutMS => {
// Useful for tests with jQuery, which is very slow in big DOMs.
let temporaryTimeoutNS = null;
export const setTestTimeoutOnce = newTimeoutMS => {
- temporaryTimeoutNS = newTimeoutMS * NS_PER_MS;
+ const newTimeoutNS = newTimeoutMS * NS_PER_MS;
+ // never accept a smaller timeout than the default
+ if (newTimeoutNS < testTimeoutNS) {
+ return;
+ }
+
+ temporaryTimeoutNS = newTimeoutNS;
};
export const initializeTestTimeout = defaultTimeoutMS => {
diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
index 40d47aaad03..246500a2f34 100644
--- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
@@ -54,5 +54,20 @@ describe('IDE commit module mutations', () => {
expect(state.shouldCreateMR).toBe(false);
});
+
+ it('sets shouldCreateMR to given value when passed in', () => {
+ state.shouldCreateMR = false;
+ mutations.TOGGLE_SHOULD_CREATE_MR(state, false);
+
+ expect(state.shouldCreateMR).toBe(false);
+ });
+ });
+
+ describe('INTERACT_WITH_NEW_MR', () => {
+ it('sets interactedWithNewMR to true', () => {
+ mutations.INTERACT_WITH_NEW_MR(state);
+
+ expect(state.interactedWithNewMR).toBe(true);
+ });
});
});
diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js
index 818404bad81..77d7478d317 100644
--- a/spec/frontend/lib/utils/number_utility_spec.js
+++ b/spec/frontend/lib/utils/number_utility_spec.js
@@ -5,6 +5,7 @@ import {
bytesToGiB,
numberToHumanSize,
sum,
+ isOdd,
} from '~/lib/utils/number_utils';
describe('Number Utils', () => {
@@ -98,4 +99,14 @@ describe('Number Utils', () => {
expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15);
});
});
+
+ describe('isOdd', () => {
+ it('should return 0 with a even number', () => {
+ expect(isOdd(2)).toEqual(0);
+ });
+
+ it('should return 1 with a odd number', () => {
+ expect(isOdd(1)).toEqual(1);
+ });
+ });
});
diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js
index 3b609484b9e..d4a3073374a 100644
--- a/spec/frontend/reports/components/report_section_spec.js
+++ b/spec/frontend/reports/components/report_section_spec.js
@@ -197,4 +197,44 @@ describe('Report section', () => {
expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand');
});
});
+
+ describe('Success and Error slots', () => {
+ const createComponent = status => {
+ vm = mountComponentWithSlots(ReportSection, {
+ props: {
+ status,
+ hasIssues: true,
+ },
+ slots: {
+ success: ['This is a success'],
+ loading: ['This is loading'],
+ error: ['This is an error'],
+ },
+ });
+ };
+
+ it('only renders success slot when status is "SUCCESS"', () => {
+ createComponent('SUCCESS');
+
+ expect(vm.$el.textContent.trim()).toContain('This is a success');
+ expect(vm.$el.textContent.trim()).not.toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is loading');
+ });
+
+ it('only renders error slot when status is "ERROR"', () => {
+ createComponent('ERROR');
+
+ expect(vm.$el.textContent.trim()).toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is a success');
+ expect(vm.$el.textContent.trim()).not.toContain('This is loading');
+ });
+
+ it('only renders loading slot when status is "LOADING"', () => {
+ createComponent('LOADING');
+
+ expect(vm.$el.textContent.trim()).toContain('This is loading');
+ expect(vm.$el.textContent.trim()).not.toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is a success');
+ });
+ });
});
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index c24f0bc4776..7e7cc1488b8 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -15,7 +15,7 @@ afterEach(() =>
}),
);
-initializeTestTimeout(500);
+initializeTestTimeout(process.env.CI ? 5000 : 500);
// fail tests for unmocked requests
beforeEach(done => {
diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
index 4a8de5fc4f1..63880b85625 100644
--- a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
@@ -15,31 +15,37 @@ function formatWarning(string) {
describe('Issue Warning Component', () => {
describe('isLocked', () => {
it('should render locked issue warning information', () => {
- const vm = mountComponent(IssueWarning, {
+ const props = {
isLocked: true,
- });
+ lockedIssueDocsPath: 'docs/issues/locked',
+ };
+ const vm = mountComponent(IssueWarning, props);
expect(
vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
).toMatch(/lock$/);
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
- 'This issue is locked. Only project members can comment.',
+ 'This issue is locked. Only project members can comment. Learn more',
);
+ expect(vm.$el.querySelector('a').href).toContain(props.lockedIssueDocsPath);
});
});
describe('isConfidential', () => {
it('should render confidential issue warning information', () => {
- const vm = mountComponent(IssueWarning, {
+ const props = {
isConfidential: true,
- });
+ confidentialIssueDocsPath: '/docs/issues/confidential',
+ };
+ const vm = mountComponent(IssueWarning, props);
expect(
vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
).toMatch(/eye-slash$/);
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
- 'This is a confidential issue. Your comment will not be visible to the public.',
+ 'This is a confidential issue. People without permission will never get a notification. Learn more',
);
+ expect(vm.$el.querySelector('a').href).toContain(props.confidentialIssueDocsPath);
});
});
diff --git a/spec/frontend/vue_shared/components/modal_copy_button_spec.js b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
new file mode 100644
index 00000000000..f1943861523
--- /dev/null
+++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
@@ -0,0 +1,40 @@
+import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import modalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+
+describe('modal copy button', () => {
+ const Component = Vue.extend(modalCopyButton);
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ beforeEach(() => {
+ wrapper = shallowMount(Component, {
+ propsData: {
+ text: 'copy me',
+ title: 'Copy this value into Clipboard!',
+ },
+ });
+ });
+
+ describe('clipboard', () => {
+ it('should fire a `success` event on click', () => {
+ document.execCommand = jest.fn(() => true);
+ window.getSelection = jest.fn(() => ({
+ toString: jest.fn(() => 'test'),
+ removeAllRanges: jest.fn(),
+ }));
+ wrapper.trigger('click');
+ expect(wrapper.emitted().success).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+ });
+ it("should propagate the clipboard error event if execCommand doesn't work", () => {
+ document.execCommand = jest.fn(() => false);
+ wrapper.trigger('click');
+ expect(wrapper.emitted().error).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/droplab_dropdown_button_spec.js b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js
new file mode 100644
index 00000000000..22295721328
--- /dev/null
+++ b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js
@@ -0,0 +1,136 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+
+import DroplabDropdownButton from '~/vue_shared/components/droplab_dropdown_button.vue';
+
+const mockActions = [
+ {
+ title: 'Foo',
+ description: 'Some foo action',
+ },
+ {
+ title: 'Bar',
+ description: 'Some bar action',
+ },
+];
+
+const createComponent = ({
+ size = '',
+ dropdownClass = '',
+ actions = mockActions,
+ defaultAction = 0,
+}) => {
+ const localVue = createLocalVue();
+
+ return mount(DroplabDropdownButton, {
+ localVue,
+ propsData: {
+ size,
+ dropdownClass,
+ actions,
+ defaultAction,
+ },
+ });
+};
+
+describe('DroplabDropdownButton', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = createComponent({});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('data', () => {
+ it('contains `selectedAction` representing value of `defaultAction` prop', () => {
+ expect(wrapper.vm.selectedAction).toBe(0);
+ });
+ });
+
+ describe('computed', () => {
+ describe('selectedActionTitle', () => {
+ it('returns string containing title of selected action', () => {
+ wrapper.setData({ selectedAction: 0 });
+
+ expect(wrapper.vm.selectedActionTitle).toBe(mockActions[0].title);
+
+ wrapper.setData({ selectedAction: 1 });
+
+ expect(wrapper.vm.selectedActionTitle).toBe(mockActions[1].title);
+ });
+ });
+
+ describe('buttonSizeClass', () => {
+ it('returns string containing button sizing class based on `size` prop', done => {
+ const wrapperWithSize = createComponent({
+ size: 'sm',
+ });
+
+ wrapperWithSize.vm.$nextTick(() => {
+ expect(wrapperWithSize.vm.buttonSizeClass).toBe('btn-sm');
+
+ done();
+ wrapperWithSize.destroy();
+ });
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('handlePrimaryActionClick', () => {
+ it('emits `onActionClick` event on component with selectedAction object as param', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+
+ wrapper.setData({ selectedAction: 0 });
+ wrapper.vm.handlePrimaryActionClick();
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionClick', mockActions[0]);
+ });
+ });
+
+ describe('handleActionClick', () => {
+ it('emits `onActionSelect` event on component with selectedAction index as param', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+
+ wrapper.vm.handleActionClick(1);
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionSelect', 1);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders default action button', () => {
+ const defaultButton = wrapper.findAll('.btn').at(0);
+
+ expect(defaultButton.text()).toBe(mockActions[0].title);
+ });
+
+ it('renders dropdown button', () => {
+ const dropdownButton = wrapper.findAll('.dropdown-toggle').at(0);
+
+ expect(dropdownButton.isVisible()).toBe(true);
+ });
+
+ it('renders dropdown actions', () => {
+ const dropdownActions = wrapper.findAll('.dropdown-menu li button');
+
+ Array(dropdownActions.length)
+ .fill()
+ .forEach((_, index) => {
+ const actionContent = dropdownActions.at(index).find('.description');
+
+ expect(actionContent.find('strong').text()).toBe(mockActions[index].title);
+ expect(actionContent.find('p').text()).toBe(mockActions[index].description);
+ });
+ });
+
+ it('renders divider between dropdown actions', () => {
+ const dropdownDivider = wrapper.find('.dropdown-menu .divider');
+
+ expect(dropdownDivider.isVisible()).toBe(true);
+ });
+ });
+});
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index 9982288e206..c162fdbbb47 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -29,18 +29,20 @@ describe Resolvers::BaseResolver do
end
end
- it 'increases complexity based on arguments' do
- field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 1)
+ context 'when field is a connection' do
+ it 'increases complexity based on arguments' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1)
- expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3
- expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7
- end
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3
+ expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7
+ end
- it 'does not increase complexity when filtering by iids' do
- field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100)
+ it 'does not increase complexity when filtering by iids' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
- expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6
- expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3
- expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
+ end
end
end
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index bffcdbfe915..798fe00de97 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -121,7 +121,7 @@ describe Resolvers::IssuesResolver do
end
it 'increases field complexity based on arguments' do
- field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100)
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4
expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 395e08081d3..20e197e9f73 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -57,7 +57,7 @@ describe Resolvers::NamespaceProjectsResolver, :nested_groups do
end
it 'has an high complexity regardless of arguments' do
- field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100)
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24
expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index a7fb156d9a8..0d3c3e37daf 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -28,18 +28,29 @@ describe Types::BaseField do
expect(field.to_graphql.complexity).to eq 12
end
- it 'sets complexity depending on arguments for resolvers' do
- field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
+ context 'when field has a resolver proc' do
+ context 'and is a connection' do
+ let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, max_page_size: 100, null: true) }
- expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4
- expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3
- end
+ it 'sets complexity depending on arguments for resolvers' do
+ expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4
+ expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3
+ end
- it 'sets complexity depending on number load limits for resolvers' do
- field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
+ it 'sets complexity depending on number load limits for resolvers' do
+ expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2
+ expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
+ end
+ end
- expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2
- expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
+ context 'and is not a connection' do
+ it 'sets complexity as normal' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
+
+ expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2
+ expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2
+ end
+ end
end
end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index e565ac8c530..25a2fcf5a81 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -162,4 +162,49 @@ describe VisibilityLevelHelper do
end
end
end
+
+ describe "selected_visibility_level" do
+ let(:group) { create(:group, :public) }
+ let!(:project) { create(:project, :internal, group: group) }
+ let!(:forked_project) { fork_project(project) }
+
+ using RSpec::Parameterized::TableSyntax
+
+ PUBLIC = Gitlab::VisibilityLevel::PUBLIC
+ INTERNAL = Gitlab::VisibilityLevel::INTERNAL
+ PRIVATE = Gitlab::VisibilityLevel::PRIVATE
+
+ # This is a subset of all the permutations
+ where(:requested_level, :max_allowed, :global_default_level, :restricted_levels, :expected) do
+ PUBLIC | PUBLIC | PUBLIC | [] | PUBLIC
+ PUBLIC | PUBLIC | PUBLIC | [PUBLIC] | INTERNAL
+ INTERNAL | PUBLIC | PUBLIC | [] | INTERNAL
+ INTERNAL | PRIVATE | PRIVATE | [] | PRIVATE
+ PRIVATE | PUBLIC | PUBLIC | [] | PRIVATE
+ PUBLIC | PRIVATE | INTERNAL | [] | PRIVATE
+ PUBLIC | INTERNAL | PUBLIC | [] | INTERNAL
+ PUBLIC | PRIVATE | PUBLIC | [] | PRIVATE
+ PUBLIC | INTERNAL | INTERNAL | [] | INTERNAL
+ PUBLIC | PUBLIC | INTERNAL | [] | PUBLIC
+ end
+
+ before do
+ stub_application_setting(restricted_visibility_levels: restricted_levels,
+ default_project_visibility: global_default_level)
+ end
+
+ with_them do
+ it "provides correct visibility level for forked project" do
+ project.update(visibility_level: max_allowed)
+
+ expect(selected_visibility_level(forked_project, requested_level)).to eq(expected)
+ end
+
+ it "provides correct visibiility level for project in group" do
+ project.group.update(visibility_level: max_allowed)
+
+ expect(selected_visibility_level(project, requested_level)).to eq(expected)
+ end
+ end
+ end
end
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index e1017130bed..13b708a03d5 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -7,8 +7,8 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import eventHub from '~/boards/eventhub';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/list';
import boardsStore from '~/boards/stores/boards_store';
import boardCard from '~/boards/components/board_card.vue';
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index b5559db8784..e81115e10c9 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -6,8 +6,8 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index a5bf97bdcc2..8a20911cc66 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -4,8 +4,8 @@
import Vue from 'vue';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index e4ff3eb381f..bb7abe52eae 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -1,8 +1,8 @@
/* global ListIssue */
import Vue from 'vue';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index bb6fc6c693d..15c9ff6dfb4 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -4,8 +4,8 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index 2839922fbd3..e6a969bd855 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -32,6 +32,7 @@ describe('AjaxFormVariableList', () => {
saveButton,
errorBox,
saveEndpoint: container.dataset.saveEndpoint,
+ maskableRegex: container.dataset.maskableRegex,
});
spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough();
@@ -220,4 +221,11 @@ describe('AjaxFormVariableList', () => {
expect(row.dataset.isPersisted).toEqual('true');
});
});
+
+ describe('maskableRegex', () => {
+ it('takes in the regex provided by the data attribute', () => {
+ expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex);
+ });
+ });
});
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 394e60fc22c..064113e879a 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -150,6 +150,65 @@ describe('VariableList', () => {
.then(done)
.catch(done.fail);
});
+
+ describe('validateMaskability', () => {
+ let $row;
+
+ const maskingErrorElement = '.js-row:last-child .masking-validation-error';
+
+ beforeEach(() => {
+ $row = $wrapper.find('.js-row:last-child');
+ $row.find('.ci-variable-masked-item .js-project-feature-toggle').click();
+ });
+
+ it('has a regex provided via a data attribute', () => {
+ expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ });
+
+ it('allows values that are 8 characters long', done => {
+ $row.find('.js-ci-variable-input-value').val('looooong');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toHaveClass('hide');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('rejects values that are shorter than 8 characters', done => {
+ $row.find('.js-ci-variable-input-value').val('short');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toBeVisible();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('allows values with base 64 characters', done => {
+ $row.find('.js-ci-variable-input-value').val('abcABC123_+=/-');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toHaveClass('hide');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('rejects values with other special characters', done => {
+ $row.find('.js-ci-variable-input-value').val('1234567$');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toBeVisible();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
describe('toggleEnableRow method', () => {
diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
index 23e6a055518..b903abe63fc 100644
--- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
@@ -73,47 +73,4 @@ describe('IDE commit sidebar actions', () => {
expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc);
});
});
-
- describe('create new MR checkbox', () => {
- it('disables `createMR` button when an MR already exists and committing to current branch', () => {
- createComponent({ hasMR: true, commitAction: consts.COMMIT_TO_CURRENT_BRANCH });
-
- expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(true);
- });
-
- it('does not disable checkbox if MR does not exist', () => {
- createComponent({ hasMR: false });
-
- expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false);
- });
-
- it('does not disable checkbox when creating a new branch', () => {
- createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH });
-
- expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false);
- });
-
- it('toggles off new MR when switching back to commit to current branch and MR exists', () => {
- createComponent({
- commitAction: consts.COMMIT_TO_NEW_BRANCH,
- shouldCreateMR: true,
- });
- const currentBranchRadio = vm.$el.querySelector(
- `input[value="${consts.COMMIT_TO_CURRENT_BRANCH}"`,
- );
- currentBranchRadio.click();
-
- vm.$nextTick(() => {
- expect(vm.$store.state.commit.shouldCreateMR).toBe(false);
- });
- });
-
- it('toggles `shouldCreateMR` when clicking checkbox', () => {
- createComponent();
- const el = vm.$el.querySelector('input[type="checkbox"]');
- el.dispatchEvent(new Event('change'));
-
- expect(vm.$store.state.commit.shouldCreateMR).toBe(true);
- });
- });
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
new file mode 100644
index 00000000000..7017bfcd6a6
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
@@ -0,0 +1,73 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import consts from '~/ide/stores/modules/commit/constants';
+import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { projectData } from 'spec/ide/mock_data';
+import { resetStore } from 'spec/ide/helpers';
+
+describe('create new MR checkbox', () => {
+ let vm;
+ const createComponent = ({
+ hasMR = false,
+ commitAction = consts.COMMIT_TO_NEW_BRANCH,
+ currentBranchId = 'master',
+ } = {}) => {
+ const Component = Vue.extend(NewMergeRequestOption);
+
+ vm = createComponentWithStore(Component, store);
+
+ vm.$store.state.currentBranchId = currentBranchId;
+ vm.$store.state.currentProjectId = 'abcproject';
+ vm.$store.state.commit.commitAction = commitAction;
+ Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
+
+ if (hasMR) {
+ vm.$store.state.currentMergeRequestId = '1';
+ vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
+ store.state.currentMergeRequestId
+ ] = { foo: 'bar' };
+ }
+
+ return vm.$mount();
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(vm.$store);
+ });
+
+ it('is hidden when an MR already exists and committing to current branch', () => {
+ createComponent({
+ hasMR: true,
+ commitAction: consts.COMMIT_TO_CURRENT_BRANCH,
+ currentBranchId: 'feature',
+ });
+
+ expect(vm.$el.textContent).toBe('');
+ });
+
+ it('does not hide checkbox if MR does not exist', () => {
+ createComponent({ hasMR: false });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ });
+
+ it('does not hide checkbox when creating a new branch', () => {
+ createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ });
+
+ it('dispatches toggleShouldCreateMR when clicking checkbox', () => {
+ createComponent();
+ const el = vm.$el.querySelector('input[type="checkbox"]');
+ spyOn(vm.$store, 'dispatch');
+ el.dispatchEvent(new Event('change'));
+
+ expect(vm.$store.dispatch.calls.allArgs()).toEqual(
+ jasmine.arrayContaining([['commit/toggleShouldCreateMR', jasmine.any(Object)]]),
+ );
+ });
+});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index 4fe826943b2..570a396c5e3 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -16,6 +16,7 @@ export const projectData = {
},
mergeRequests: {},
merge_requests_enabled: true,
+ default_branch: 'master',
};
export const pipelines = [
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index e6fb08bcc49..dd2313dc800 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -719,4 +719,20 @@ describe('IDE store file actions', () => {
.catch(done.fail);
});
});
+
+ describe('triggerFilesChange', () => {
+ beforeEach(() => {
+ spyOn(eventHub, '$emit');
+ });
+
+ it('emits event that files have changed', done => {
+ store
+ .dispatch('triggerFilesChange')
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index 04e236fb042..37354283cab 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -488,7 +488,7 @@ describe('Multi-file store actions', () => {
'path',
store.state,
[{ type: types.DELETE_ENTRY, payload: 'path' }],
- [{ type: 'burstUnusedSeal' }],
+ [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }],
done,
);
});
@@ -510,7 +510,7 @@ describe('Multi-file store actions', () => {
payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
},
],
- [{ type: 'deleteEntry', payload: 'test' }],
+ [{ type: 'deleteEntry', payload: 'test' }, { type: 'triggerFilesChange' }],
done,
);
});
@@ -558,6 +558,7 @@ describe('Multi-file store actions', () => {
},
},
{ type: 'deleteEntry', payload: 'test' },
+ { type: 'triggerFilesChange' },
],
done,
);
diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js
index 9c135661997..735bbd47f55 100644
--- a/spec/javascripts/ide/stores/getters_spec.js
+++ b/spec/javascripts/ide/stores/getters_spec.js
@@ -180,6 +180,38 @@ describe('IDE store getters', () => {
});
});
+ describe('isOnDefaultBranch', () => {
+ it('returns false when no project exists', () => {
+ const localGetters = {
+ currentProject: undefined,
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy();
+ });
+
+ it("returns true when project's default branch matches current branch", () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ branchName: 'master',
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeTruthy();
+ });
+
+ it("returns false when project's default branch doesn't match current branch", () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ branchName: 'feature',
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy();
+ });
+ });
+
describe('packageJson', () => {
it('returns package.json entry', () => {
localState.entries['package.json'] = { name: 'package.json' };
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 4413a12fac4..5f7272311c8 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -1,9 +1,12 @@
-import actions from '~/ide/stores/actions';
+import rootActions from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
import router from '~/ide/ide_router';
import eventHub from '~/ide/eventhub';
import consts from '~/ide/stores/modules/commit/constants';
+import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types';
+import * as actions from '~/ide/stores/modules/commit/actions';
+import testAction from '../../../../helpers/vuex_action_helper';
import { commitActionTypes } from '~/ide/constants';
import { resetStore, file } from 'spec/ide/helpers';
@@ -225,7 +228,7 @@ describe('IDE commit module actions', () => {
let visitUrl;
beforeEach(() => {
- visitUrl = spyOnDependency(actions, 'visitUrl');
+ visitUrl = spyOnDependency(rootActions, 'visitUrl');
document.body.innerHTML += '<div class="flash-container"></div>';
@@ -523,4 +526,154 @@ describe('IDE commit module actions', () => {
});
});
});
+
+ describe('toggleShouldCreateMR', () => {
+ it('commits both toggle and interacting with MR checkbox actions', done => {
+ testAction(
+ actions.toggleShouldCreateMR,
+ {},
+ store.state,
+ [
+ { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR },
+ { type: mutationTypes.INTERACT_WITH_NEW_MR },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setShouldCreateMR', () => {
+ beforeEach(() => {
+ store.state.projects = {
+ project: {
+ default_branch: 'master',
+ branches: {
+ master: {
+ name: 'master',
+ },
+ feature: {
+ name: 'feature',
+ },
+ },
+ },
+ };
+
+ store.state.currentProjectId = 'project';
+ });
+
+ it('sets to false when the current branch already has an MR', done => {
+ store.state.commit.currentMergeRequestId = 1;
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.currentBranchId = 'feature';
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('changes to false when current branch is the default branch and user has not interacted', done => {
+ store.state.commit.interactedWithNewMR = false;
+ store.state.currentBranchId = 'master';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('changes to true when "create new branch" is selected and user has not interacted', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.interactedWithNewMR = false;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, true]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change anything if user has interacted and comitting to new branch', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change anything if user has interacted and comitting to branch without MR', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.commit.currentMergeRequestId = null;
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('still changes to false if hiding the checkbox', done => {
+ store.state.currentBranchId = 'feature';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change to false when on master and user has interacted even if MR exists', done => {
+ store.state.currentBranchId = 'master';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
index e00fd7199d7..6e71a790deb 100644
--- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -144,33 +144,4 @@ describe('IDE commit module getters', () => {
});
});
});
-
- describe('shouldDisableNewMrOption', () => {
- it('returns false if commitAction `COMMIT_TO_NEW_BRANCH`', () => {
- state.commitAction = consts.COMMIT_TO_NEW_BRANCH;
- const rootState = {
- currentMergeRequest: { foo: 'bar' },
- };
-
- expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy();
- });
-
- it('returns false if there is no current merge request', () => {
- state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- const rootState = {
- currentMergeRequest: null,
- };
-
- expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy();
- });
-
- it('returns true an MR exists and commit action is `COMMIT_TO_CURRENT_BRANCH`', () => {
- state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- const rootState = {
- currentMergeRequest: { foo: 'bar' },
- };
-
- expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeTruthy();
- });
- });
});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index cea8cb18918..1a371c3adaf 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -38,6 +38,7 @@ describe('Dashboard', () => {
let DashboardComponent;
let mock;
let store;
+ let component;
beforeEach(() => {
setFixtures(`
@@ -59,14 +60,15 @@ describe('Dashboard', () => {
});
afterEach(() => {
+ component.$destroy();
mock.restore();
});
describe('no metrics are available yet', () => {
it('shows a getting started empty state when no metrics are present', () => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, showTimeWindowDropdown: false },
+ propsData: { ...propsData },
store,
});
@@ -81,9 +83,9 @@ describe('Dashboard', () => {
});
it('shows up a loading state', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false },
+ propsData: { ...propsData, hasMetrics: true },
store,
});
@@ -94,13 +96,12 @@ describe('Dashboard', () => {
});
it('hides the legend when showLegend is false', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showLegend: false,
- showTimeWindowDropdown: false,
},
store,
});
@@ -114,13 +115,12 @@ describe('Dashboard', () => {
});
it('hides the group panels when showPanels is false', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
store,
});
@@ -134,13 +134,12 @@ describe('Dashboard', () => {
});
it('renders the environments dropdown with a number of environments', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
store,
});
@@ -165,13 +164,12 @@ describe('Dashboard', () => {
});
it('hides the environments dropdown list when there is no environments', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
store,
});
@@ -195,13 +193,12 @@ describe('Dashboard', () => {
});
it('renders the environments dropdown with a single active element', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
store,
});
@@ -228,14 +225,13 @@ describe('Dashboard', () => {
});
it('hides the dropdown', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
environmentsEndpoint: '',
- showTimeWindowDropdown: false,
},
store,
});
@@ -248,59 +244,72 @@ describe('Dashboard', () => {
});
});
- it('does not show the time window dropdown when the feature flag is not set', done => {
- const component = new DashboardComponent({
+ it('renders the time window dropdown with a set of options', done => {
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
store,
});
+ const numberOfTimeWindows = Object.keys(timeWindows).length;
setTimeout(() => {
const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
+ const timeWindowDropdownEls = component.$el.querySelectorAll(
+ '.js-time-window-dropdown .dropdown-item',
+ );
- expect(timeWindowDropdown).toBeNull();
+ expect(timeWindowDropdown).not.toBeNull();
+ expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
done();
});
});
- it('renders the time window dropdown with a set of options', done => {
- const component = new DashboardComponent({
+ it('fetches the metrics data with proper time window', done => {
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: true,
},
store,
});
- const numberOfTimeWindows = Object.keys(timeWindows).length;
- setTimeout(() => {
- const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
- const timeWindowDropdownEls = component.$el.querySelectorAll(
- '.js-time-window-dropdown .dropdown-item',
- );
+ spyOn(component.$store, 'dispatch').and.stub();
+ const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff');
- expect(timeWindowDropdown).not.toBeNull();
- expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
+ component.$store.commit(
+ `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`,
+ '/environments',
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
- done();
- });
+ component.$mount();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(component.$store.dispatch).toHaveBeenCalled();
+ expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow);
+
+ done();
+ })
+ .catch(done.fail);
});
it('shows a specific time window selected from the url params', done => {
spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']);
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true },
+ propsData: { ...propsData, hasMetrics: true },
store,
});
@@ -319,9 +328,9 @@ describe('Dashboard', () => {
'<script>alert("XSS")</script>',
]);
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true },
+ propsData: { ...propsData, hasMetrics: true },
store,
});
@@ -344,13 +353,12 @@ describe('Dashboard', () => {
});
it('sets elWidth to page width when the sidebar is resized', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
store,
});
@@ -374,16 +382,10 @@ describe('Dashboard', () => {
});
describe('external dashboard link', () => {
- let component;
-
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
});
- afterEach(() => {
- component.$destroy();
- });
-
describe('with feature flag enabled', () => {
beforeEach(() => {
component = new DashboardComponent({
@@ -392,7 +394,6 @@ describe('Dashboard', () => {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
externalDashboardUrl: '/mockUrl',
},
store,
@@ -418,7 +419,6 @@ describe('Dashboard', () => {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
externalDashboardUrl: '',
},
store,
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index 2159e4ddf16..1f2c07385a7 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -66,7 +66,7 @@ describe('noteActions', () => {
expect(wrapper.find('.js-note-edit').exists()).toBe(true);
});
- it('should be possible to report abuse to GitLab', () => {
+ it('should be possible to report abuse to admin', () => {
expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true);
});
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index 6e215ea1561..c788da55cd2 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -2,8 +2,12 @@ require 'spec_helper'
describe API::Helpers::Pagination do
let(:resource) { Project.all }
- let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:8080/api/v4/projects" }
- let(:canonical_api_projects_url) { "#{Gitlab.config.gitlab.url}/api/v4/projects" }
+ let(:custom_port) { 8080 }
+ let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" }
+
+ before do
+ stub_config_setting(port: custom_port)
+ end
subject do
Class.new.include(described_class).new
@@ -48,7 +52,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
@@ -71,7 +75,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
@@ -171,7 +175,7 @@ describe API::Helpers::Pagination do
it 'returns the right link to the next page' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
end
@@ -224,9 +228,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
expect(val).not_to include('rel="prev"')
end
@@ -290,8 +294,8 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
expect(val).not_to include('rel="last"')
expect(val).not_to include('rel="prev"')
end
@@ -318,9 +322,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '1')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev"))
expect(val).not_to include('rel="next"')
end
@@ -367,8 +371,8 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last"))
expect(val).not_to include('rel="prev"')
expect(val).not_to include('rel="next"')
expect(val).not_to include('page=0')
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index 43222ddb5e2..7c94cf37e32 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -155,6 +155,13 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do
it_behaves_like "external issue tracker"
end
+
+ context "with a lowercase prefix" do
+ let(:issue) { ExternalIssue.new("gl-030", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
end
context "jira project" do
diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
index 27281333348..0a5b99d27e7 100644
--- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
@@ -3,6 +3,12 @@ require 'spec_helper'
# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do
describe '#perform' do
+ before do
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ end
+
context 'when diff files can be deleted' do
let(:merge_request) { create(:merge_request, :merged) }
let!(:merge_request_diff) do
diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
index 3e009fed0f1..c6bc3db88a3 100644
--- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
@@ -9,6 +9,9 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration
before do
# This migration was created before we introduced metadata configs
stub_feature_flags(ci_build_metadata_config: false)
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
end
let!(:internal_pipeline) { create(:ci_pipeline, source: :web) }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
new file mode 100644
index 00000000000..006ce4d8078
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
@@ -0,0 +1,77 @@
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do
+ let(:left) { double('left', evaluate: nil) }
+ let(:right) { double('right', evaluate: nil) }
+
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('&&', left, right)).to be_a(described_class)
+ end
+
+ context 'with non-evaluable operands' do
+ let(:left) { double('left') }
+ let(:right) { double('right') }
+
+ it 'raises an operator error' do
+ expect { described_class.build('&&', left, right) }.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+ end
+
+ describe '.type' do
+ it 'is an operator' do
+ expect(described_class.type).to eq :operator
+ end
+ end
+
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
+ describe '#evaluate' do
+ let(:operator) { described_class.new(left, right) }
+
+ subject { operator.evaluate }
+
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
+ end
+
+ context 'when left and right are truthy' do
+ where(:left_value, :right_value) do
+ [true, 1, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ it { is_expected.to eq(right_value) }
+ end
+ end
+
+ context 'when left or right is falsey' do
+ where(:left_value, :right_value) do
+ [true, false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when left and right are falsey' do
+ where(:left_value, :right_value) do
+ [false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ it { is_expected.to eq(left_value) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
index 019a2ed184d..fcbd2863289 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -5,9 +5,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('==', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('==', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('==', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -17,23 +29,40 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns false when left and right are not equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(2)
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate(VARIABLE: 3)).to eq false
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns true when left and right are equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(1)
+ context 'when left and right are equal' do
+ where(:left_value, :right_value) do
+ [%w(string string)]
+ end
+
+ with_them do
+ it { is_expected.to eq(true) }
+ end
+ end
- operator = described_class.new(left, right)
+ context 'when left and right are not equal' do
+ where(:left_value, :right_value) do
+ ['one string', 'two string'].permutation(2).to_a
+ end
- expect(operator.evaluate(VARIABLE: 3)).to eq true
+ with_them do
+ it { is_expected.to eq(false) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index 49e5af52f4d..97da66d2bcc 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -6,9 +6,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('=~', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('=~', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('/my-string/')
+
+ expect(described_class.build('=~', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -18,63 +30,93 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns false when left and right do not match' do
- allow(left).to receive(:evaluate).and_return('my-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('something'))
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate).to eq false
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns true when left and right match' do
- allow(left).to receive(:evaluate).and_return('my-awesome-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('awesome.string$'))
+ context 'when left and right do not match' do
+ let(:left_value) { 'my-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('something') }
- operator = described_class.new(left, right)
-
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
- it 'supports matching against a nil value' do
- allow(left).to receive(:evaluate).and_return(nil)
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('pattern'))
+ context 'when left and right match' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') }
+
+ it { is_expected.to eq(3) }
+ end
- operator = described_class.new(left, right)
+ context 'when left is nil' do
+ let(:left_value) { nil }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(nil) }
end
- it 'supports multiline strings' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My awesome contents
+ context 'when left is a multiline string and matches right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
+
+ My-text-string!
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
+
+ it { is_expected.to eq(24) }
+ end
- My-text-string!
- TEXT
+ context 'when left is a multiline string and does not match right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('text-string'))
+ My-terrible-string!
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
- it 'supports regexp flags' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My AWESOME content
- TEXT
+ context 'when a matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') }
+
+ it { is_expected.to eq(3) }
+ end
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome'))
+ context 'when a non-matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') }
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
index 9aa2f4efd67..38d30c9035a 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
@@ -5,9 +5,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('!=', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('!=', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('!=', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -17,23 +29,45 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns true when left and right are not equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(2)
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate(VARIABLE: 3)).to eq true
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns false when left and right are equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(1)
+ context 'when left and right are equal' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:left_value, :right_value) do
+ 'string' | 'string'
+ 1 | 1
+ '' | ''
+ nil | nil
+ end
+
+ with_them do
+ it { is_expected.to eq(false) }
+ end
+ end
- operator = described_class.new(left, right)
+ context 'when left and right are not equal' do
+ where(:left_value, :right_value) do
+ ['one string', 'two string', 1, 2, '', nil, false, true].permutation(2).to_a
+ end
- expect(operator.evaluate(VARIABLE: 3)).to eq false
+ with_them do
+ it { is_expected.to eq(true) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
index fa3b9651fb4..99110ff8d88 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -6,9 +6,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('!~', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('!~', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('!~', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -18,63 +30,93 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns true when left and right do not match' do
- allow(left).to receive(:evaluate).and_return('my-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('something'))
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate).to eq true
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns false when left and right match' do
- allow(left).to receive(:evaluate).and_return('my-awesome-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('awesome.string$'))
+ context 'when left and right do not match' do
+ let(:left_value) { 'my-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('something') }
- operator = described_class.new(left, right)
-
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(true) }
end
- it 'supports matching against a nil value' do
- allow(left).to receive(:evaluate).and_return(nil)
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('pattern'))
+ context 'when left and right match' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') }
+
+ it { is_expected.to eq(false) }
+ end
- operator = described_class.new(left, right)
+ context 'when left is nil' do
+ let(:left_value) { nil }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(true) }
end
- it 'supports multiline strings' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My awesome contents
+ context 'when left is a multiline string and matches right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
+
+ My-text-string!
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
+
+ it { is_expected.to eq(false) }
+ end
- My-text-string!
- TEXT
+ context 'when left is a multiline string and does not match right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('text-string'))
+ My-terrible-string!
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(true) }
end
- it 'supports regexp flags' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My AWESOME content
- TEXT
+ context 'when a matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') }
+
+ it { is_expected.to eq(false) }
+ end
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome'))
+ context 'when a non-matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') }
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(true) }
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
new file mode 100644
index 00000000000..d542eebc613
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
@@ -0,0 +1,77 @@
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do
+ let(:left) { double('left', evaluate: nil) }
+ let(:right) { double('right', evaluate: nil) }
+
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('||', left, right)).to be_a(described_class)
+ end
+
+ context 'with non-evaluable operands' do
+ let(:left) { double('left') }
+ let(:right) { double('right') }
+
+ it 'raises an operator error' do
+ expect { described_class.build('||', left, right) }.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+ end
+
+ describe '.type' do
+ it 'is an operator' do
+ expect(described_class.type).to eq :operator
+ end
+ end
+
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
+ describe '#evaluate' do
+ let(:operator) { described_class.new(left, right) }
+
+ subject { operator.evaluate }
+
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
+ end
+
+ context 'when left and right are truthy' do
+ where(:left_value, :right_value) do
+ [true, 1, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ it { is_expected.to eq(left_value) }
+ end
+ end
+
+ context 'when left or right is truthy' do
+ where(:left_value, :right_value) do
+ [true, false, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when left and right are falsey' do
+ where(:left_value, :right_value) do
+ [false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ it { is_expected.to eq(right_value) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index cff7f57ceff..30ea3f3e28e 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -1,4 +1,4 @@
-require 'fast_spec_helper'
+require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
describe '.build' do
@@ -30,16 +30,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
.to eq Gitlab::UntrustedRegexp.new('pattern')
end
- it 'is a greedy scanner for regexp boundaries' do
- scanner = StringScanner.new('/some .* / pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
- end
-
it 'does not allow to use an empty pattern' do
scanner = StringScanner.new(%(//))
@@ -68,12 +58,90 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
.to eq Gitlab::UntrustedRegexp.new('(?im)pattern')
end
- it 'does not support arbitrary flags' do
+ it 'ignores unsupported flags' do
scanner = StringScanner.new('/pattern/x')
token = described_class.scan(scanner)
- expect(token).to be_nil
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('pattern')
+ end
+
+ it 'is a eager scanner for regexp boundaries' do
+ scanner = StringScanner.new('/some .* / pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* ')
+ end
+
+ it 'does not match on escaped regexp boundaries' do
+ scanner = StringScanner.new('/some .* \/ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
+ end
+
+ it 'recognizes \ as an escape character for /' do
+ scanner = StringScanner.new('/some numeric \/$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric /$ pattern')
+ end
+
+ it 'does not recognize \ as an escape character for $' do
+ scanner = StringScanner.new('/some numeric \$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
+ end
+
+ context 'with the ci_variables_complex_expressions feature flag disabled' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
+
+ it 'is a greedy scanner for regexp boundaries' do
+ scanner = StringScanner.new('/some .* / pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
+ end
+
+ it 'does not recognize the \ escape character for /' do
+ scanner = StringScanner.new('/some .* \/ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* \/ pattern')
+ end
+
+ it 'does not recognize the \ escape character for $' do
+ scanner = StringScanner.new('/some numeric \$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index 3f11b3f7673..d8db9c262a1 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -58,6 +58,56 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
expect { lexer.tokens }
.to raise_error described_class::SyntaxError
end
+
+ context 'with complex expressions' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(expression).tokens.map(&:value) }
+
+ where(:expression, :tokens) do
+ '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '&&', '$EMPTY_VARIABLE', '=~', '/nope/']
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE']
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE', '!=', '"nope"']
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ['$PRESENT_VARIABLE', '&&', '$EMPTY_VARIABLE']
+ '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '||', '$EMPTY_VARIABLE', '=~', '/nope/']
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE']
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE != "nope"' | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE', '!=', '"nope"']
+ '$PRESENT_VARIABLE || $EMPTY_VARIABLE' | ['$PRESENT_VARIABLE', '||', '$EMPTY_VARIABLE']
+ '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""' | ['$PRESENT_VARIABLE', '&&', 'null', '||', '$EMPTY_VARIABLE', '==', '""']
+ end
+
+ with_them do
+ it { is_expected.to eq(tokens) }
+ end
+ end
+
+ context 'with the ci_variables_complex_expressions feature flag turned off' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
+
+ it 'incorrectly tokenizes conjunctive match statements as one match statement' do
+ tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens
+
+ expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/'])
+ end
+
+ it 'incorrectly tokenizes disjunctive match statements as one statement' do
+ tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens
+
+ expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/'])
+ end
+
+ it 'raises an error about && operators' do
+ expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens }
+ .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
+ end
+
+ it 'raises an error about || operators' do
+ expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens }
+ .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
+ end
+ end
end
describe '#lexemes' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
index 2b78b1dd4a7..e88ec5561b6 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
@@ -2,25 +2,67 @@ require 'fast_spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser do
describe '#tree' do
- context 'when using operators' do
+ context 'when using two operators' do
+ it 'returns a reverse descent parse tree' do
+ expect(described_class.seed('$VAR1 == "123"').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
+ end
+ end
+
+ context 'when using three operators' do
it 'returns a reverse descent parse tree' do
expect(described_class.seed('$VAR1 == "123" == $VAR2').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
end
end
- context 'when using a single token' do
+ context 'when using a single variable token' do
it 'returns a single token instance' do
expect(described_class.seed('$VAR').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
end
end
+ context 'when using a single string token' do
+ it 'returns a single token instance' do
+ expect(described_class.seed('"some value"').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::String
+ end
+ end
+
context 'when expression is empty' do
it 'returns a null token' do
- expect(described_class.seed('').tree)
+ expect { described_class.seed('').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError
+ end
+ end
+
+ context 'when expression is null' do
+ it 'returns a null token' do
+ expect(described_class.seed('null').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Null
end
end
+
+ context 'when two value tokens have no operator' do
+ it 'raises a parsing error' do
+ expect { described_class.seed('$VAR "text"').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError
+ end
+ end
+
+ context 'when an operator has no left side' do
+ it 'raises an OperatorError' do
+ expect { described_class.seed('== "123"').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'when an operator has no right side' do
+ it 'raises an OperatorError' do
+ expect { described_class.seed('$VAR ==').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index a9fd809409b..057e2f3fbe8 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,4 +1,6 @@
-require 'fast_spec_helper'
+# TODO switch this back after the "ci_variables_complex_expressions" feature flag is removed
+# require 'fast_spec_helper'
+require 'spec_helper'
require 'rspec-parameterized'
describe Gitlab::Ci::Pipeline::Expression::Statement do
@@ -7,8 +9,12 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
end
let(:variables) do
- { 'PRESENT_VARIABLE' => 'my variable',
- EMPTY_VARIABLE: '' }
+ {
+ 'PRESENT_VARIABLE' => 'my variable',
+ 'PATH_VARIABLE' => 'a/path/variable/value',
+ 'FULL_PATH_VARIABLE' => '/a/full/path/variable/value',
+ 'EMPTY_VARIABLE' => ''
+ }
end
describe '.new' do
@@ -21,105 +27,158 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
end
end
- describe '#parse_tree' do
- context 'when expression is empty' do
- let(:text) { '' }
-
- it 'raises an error' do
- expect { subject.parse_tree }
- .to raise_error described_class::StatementError
- end
- end
+ describe '#evaluate' do
+ using RSpec::Parameterized::TableSyntax
- context 'when expression grammar is incorrect' do
- table = [
- '$VAR "text"', # missing operator
- '== "123"', # invalid left side
- '"some string"', # only string provided
- '$VAR ==', # invalid right side
- 'null', # missing lexemes
- '' # empty statement
- ]
-
- table.each do |syntax|
- context "when expression grammar is #{syntax.inspect}" do
- let(:text) { syntax }
-
- it 'raises a statement error exception' do
- expect { subject.parse_tree }
- .to raise_error described_class::StatementError
- end
-
- it 'is an invalid statement' do
- expect(subject).not_to be_valid
- end
- end
- end
+ where(:expression, :value) do
+ '$PRESENT_VARIABLE == "my variable"' | true
+ '"my variable" == $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE == null' | false
+ '$EMPTY_VARIABLE == null' | false
+ '"" == $EMPTY_VARIABLE' | true
+ '$EMPTY_VARIABLE' | ''
+ '$UNDEFINED_VARIABLE == null' | true
+ 'null == $UNDEFINED_VARIABLE' | true
+ '$PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | 3
+ '$PRESENT_VARIABLE =~ /va\r.*e$/' | nil
+ '$PRESENT_VARIABLE =~ /va\/r.*e$/' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | 3
+ "$PRESENT_VARIABLE =~ /^var.*/" | nil
+ "$EMPTY_VARIABLE =~ /var.*/" | nil
+ "$UNDEFINED_VARIABLE =~ /var.*/" | nil
+ "$PRESENT_VARIABLE =~ /VAR.*/i" | 3
+ '$PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | 0
+ '$FULL_PATH_VARIABLE =~ /\\/path\\/variable\\/value$/' | 7
+ '$PRESENT_VARIABLE != "my variable"' | false
+ '"my variable" != $PRESENT_VARIABLE' | false
+ '$PRESENT_VARIABLE != null' | true
+ '$EMPTY_VARIABLE != null' | true
+ '"" != $EMPTY_VARIABLE' | false
+ '$UNDEFINED_VARIABLE != null' | false
+ 'null != $UNDEFINED_VARIABLE' | false
+ "$PRESENT_VARIABLE !~ /var.*e$/" | false
+ "$PRESENT_VARIABLE !~ /^var.*/" | true
+ '$PRESENT_VARIABLE !~ /^v\ar.*/' | true
+ '$PRESENT_VARIABLE !~ /^v\/ar.*/' | true
+ "$EMPTY_VARIABLE !~ /var.*/" | true
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
+ "$PRESENT_VARIABLE !~ /VAR.*/i" | false
+
+ '$PRESENT_VARIABLE && "string"' | 'string'
+ '$PRESENT_VARIABLE && $PRESENT_VARIABLE' | 'my variable'
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ''
+ '$PRESENT_VARIABLE && null' | nil
+ '"string" && $PRESENT_VARIABLE' | 'my variable'
+ '$EMPTY_VARIABLE && $PRESENT_VARIABLE' | 'my variable'
+ 'null && $PRESENT_VARIABLE' | nil
+ '$EMPTY_VARIABLE && "string"' | 'string'
+ '$EMPTY_VARIABLE && $EMPTY_VARIABLE' | ''
+ '"string" && $EMPTY_VARIABLE' | ''
+ '"string" && null' | nil
+ 'null && "string"' | nil
+ '"string" && "string"' | 'string'
+ 'null && null' | nil
+
+ '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | nil
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | 'my variable'
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | true
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ''
+ '$PRESENT_VARIABLE && $UNDEFINED_VARIABLE' | nil
+ '$UNDEFINED_VARIABLE && $EMPTY_VARIABLE' | nil
+ '$UNDEFINED_VARIABLE && $PRESENT_VARIABLE' | nil
+
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | nil
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 0
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | 0
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+
+ '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | 0
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE != "nope" || $EMPTY_VARIABLE == ""' | true
+
+ '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""' | true
+ '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE || $PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE == null || $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE == null' | 'my variable'
end
- context 'when expression grammar is correct' do
- context 'when using an operator' do
- let(:text) { '$VAR == "value"' }
-
- it 'returns a reverse descent parse tree' do
- expect(subject.parse_tree)
- .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
- end
+ with_them do
+ let(:text) { expression }
- it 'is a valid statement' do
- expect(subject).to be_valid
- end
+ it "evaluates to `#{params[:value].inspect}`" do
+ expect(subject.evaluate).to eq(value)
end
- context 'when using a single token' do
- let(:text) { '$PRESENT_VARIABLE' }
+ # This test is used to ensure that our parser
+ # returns exactly the same results as if we
+ # were evaluating using ruby's `eval`
+ context 'when using Ruby eval' do
+ let(:expression_ruby) do
+ expression
+ .gsub(/null/, 'nil')
+ .gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) { "variables['#{Regexp.last_match(1)}']" }
+ end
- it 'returns a single token instance' do
- expect(subject.parse_tree)
- .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
+ it 'behaves exactly the same' do
+ expect(instance_eval(expression_ruby)).to eq(subject.evaluate)
end
end
end
- end
- describe '#evaluate' do
- using RSpec::Parameterized::TableSyntax
+ context 'with the ci_variables_complex_expressions feature flag disabled' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
- where(:expression, :value) do
- '$PRESENT_VARIABLE == "my variable"' | true
- '"my variable" == $PRESENT_VARIABLE' | true
- '$PRESENT_VARIABLE == null' | false
- '$EMPTY_VARIABLE == null' | false
- '"" == $EMPTY_VARIABLE' | true
- '$EMPTY_VARIABLE' | ''
- '$UNDEFINED_VARIABLE == null' | true
- 'null == $UNDEFINED_VARIABLE' | true
- '$PRESENT_VARIABLE' | 'my variable'
- '$UNDEFINED_VARIABLE' | nil
- "$PRESENT_VARIABLE =~ /var.*e$/" | true
- "$PRESENT_VARIABLE =~ /^var.*/" | false
- "$EMPTY_VARIABLE =~ /var.*/" | false
- "$UNDEFINED_VARIABLE =~ /var.*/" | false
- "$PRESENT_VARIABLE =~ /VAR.*/i" | true
- '$PRESENT_VARIABLE != "my variable"' | false
- '"my variable" != $PRESENT_VARIABLE' | false
- '$PRESENT_VARIABLE != null' | true
- '$EMPTY_VARIABLE != null' | true
- '"" != $EMPTY_VARIABLE' | false
- '$UNDEFINED_VARIABLE != null' | false
- 'null != $UNDEFINED_VARIABLE' | false
- "$PRESENT_VARIABLE !~ /var.*e$/" | false
- "$PRESENT_VARIABLE !~ /^var.*/" | true
- "$EMPTY_VARIABLE !~ /var.*/" | true
- "$UNDEFINED_VARIABLE !~ /var.*/" | true
- "$PRESENT_VARIABLE !~ /VAR.*/i" | false
- end
+ where(:expression, :value) do
+ '$PRESENT_VARIABLE == "my variable"' | true
+ '"my variable" == $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE == null' | false
+ '$EMPTY_VARIABLE == null' | false
+ '"" == $EMPTY_VARIABLE' | true
+ '$EMPTY_VARIABLE' | ''
+ '$UNDEFINED_VARIABLE == null' | true
+ 'null == $UNDEFINED_VARIABLE' | true
+ '$PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | true
+ "$PRESENT_VARIABLE =~ /^var.*/" | false
+ "$EMPTY_VARIABLE =~ /var.*/" | false
+ "$UNDEFINED_VARIABLE =~ /var.*/" | false
+ "$PRESENT_VARIABLE =~ /VAR.*/i" | true
+ '$PATH_VARIABLE =~ /path/variable/' | true
+ '$PATH_VARIABLE =~ /path\/variable/' | true
+ '$FULL_PATH_VARIABLE =~ /^/a/full/path/variable/value$/' | true
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true
+ '$PRESENT_VARIABLE != "my variable"' | false
+ '"my variable" != $PRESENT_VARIABLE' | false
+ '$PRESENT_VARIABLE != null' | true
+ '$EMPTY_VARIABLE != null' | true
+ '"" != $EMPTY_VARIABLE' | false
+ '$UNDEFINED_VARIABLE != null' | false
+ 'null != $UNDEFINED_VARIABLE' | false
+ "$PRESENT_VARIABLE !~ /var.*e$/" | false
+ "$PRESENT_VARIABLE !~ /^var.*/" | true
+ "$EMPTY_VARIABLE !~ /var.*/" | true
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
+ "$PRESENT_VARIABLE !~ /VAR.*/i" | false
+ end
- with_them do
- let(:text) { expression }
+ with_them do
+ let(:text) { expression }
- it "evaluates to `#{params[:value].inspect}`" do
- expect(subject.evaluate).to eq value
+ it "evaluates to `#{params[:value].inspect}`" do
+ expect(subject.evaluate).to eq value
+ end
end
end
end
@@ -137,6 +196,8 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
'$INVALID = 1' | false
"$PRESENT_VARIABLE =~ /var.*/" | true
"$UNDEFINED_VARIABLE =~ /var.*/" | false
+ "$PRESENT_VARIABLE !~ /var.*/" | false
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
end
with_them do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 025439f1b6e..b6231510b91 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -163,11 +163,11 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Build::Canceled]
+ .to eq [Gitlab::Ci::Status::Build::Canceled, Gitlab::Ci::Status::Build::Retryable]
end
- it 'does not fabricate a retryable build status' do
- expect(status).not_to be_a Gitlab::Ci::Status::Build::Retryable
+ it 'fabricates a retryable build status' do
+ expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
end
it 'fabricates status with correct details' do
@@ -177,7 +177,7 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(status.illustration).to include(:image, :size, :title)
expect(status.label).to eq 'canceled'
expect(status).to have_details
- expect(status).not_to have_action
+ expect(status).to have_action
end
end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index 753c74ff814..6a6cf1429c8 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -5,39 +5,66 @@ require 'fast_spec_helper'
require 'gitlab/danger/teammate'
describe Gitlab::Danger::Teammate do
- subject { described_class.new({ 'projects' => projects }) }
+ subject { described_class.new(options) }
+ let(:options) { { 'projects' => projects, 'role' => role } }
let(:projects) { { project => capabilities } }
+ let(:role) { 'Engineer, Manage' }
+ let(:labels) { [] }
let(:project) { double }
- describe 'multiple roles project project' do
- let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer database'] }
+ context 'when having multiple capabilities' do
+ let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer qa'] }
it '#reviewer? supports multiple roles per project' do
- expect(subject.reviewer?(project, :backend)).to be_truthy
+ expect(subject.reviewer?(project, :backend, labels)).to be_truthy
end
it '#traintainer? supports multiple roles per project' do
- expect(subject.traintainer?(project, :database)).to be_truthy
+ expect(subject.traintainer?(project, :qa, labels)).to be_truthy
end
it '#maintainer? supports multiple roles per project' do
- expect(subject.maintainer?(project, :frontend)).to be_truthy
+ expect(subject.maintainer?(project, :frontend, labels)).to be_truthy
+ end
+
+ context 'when labels contain Create and the category is test' do
+ let(:labels) { ['Create'] }
+
+ context 'when role is Test Automation Engineer, Create' do
+ let(:role) { 'Test Automation Engineer, Create' }
+
+ it '#reviewer? returns true' do
+ expect(subject.reviewer?(project, :test, labels)).to be_truthy
+ end
+
+ it '#maintainer? returns false' do
+ expect(subject.maintainer?(project, :test, labels)).to be_falsey
+ end
+ end
+
+ context 'when role is Test Automation Engineer, Manage' do
+ let(:role) { 'Test Automation Engineer, Manage' }
+
+ it '#reviewer? returns false' do
+ expect(subject.reviewer?(project, :test, labels)).to be_falsey
+ end
+ end
end
end
- describe 'one role project project' do
+ context 'when having single capability' do
let(:capabilities) { 'reviewer backend' }
it '#reviewer? supports one role per project' do
- expect(subject.reviewer?(project, :backend)).to be_truthy
+ expect(subject.reviewer?(project, :backend, labels)).to be_truthy
end
it '#traintainer? supports one role per project' do
- expect(subject.traintainer?(project, :database)).to be_falsey
+ expect(subject.traintainer?(project, :database, labels)).to be_falsey
end
it '#maintainer? supports one role per project' do
- expect(subject.maintainer?(project, :frontend)).to be_falsey
+ expect(subject.maintainer?(project, :frontend, labels)).to be_falsey
end
end
end
diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
index 74622f356de..fcd92586362 100644
--- a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
@@ -3,23 +3,11 @@
require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Challenge do
- delegated_methods = {
- url: 'https://example.com/',
- status: 'pending',
- token: 'tokenvalue',
- file_content: 'hereisfilecontent',
- request_validation: true
- }
+ include LetsEncryptHelpers
- let(:acme_challenge) do
- acme_challenge = instance_double('Acme::Client::Resources::Challenge')
- allow(acme_challenge).to receive_messages(delegated_methods)
- acme_challenge
- end
-
- let(:challenge) { described_class.new(acme_challenge) }
+ let(:challenge) { described_class.new(acme_challenge_double) }
- delegated_methods.each do |method, value|
+ LetsEncryptHelpers::ACME_CHALLENGE_METHODS.each do |method, value|
describe "##{method}" do
it 'delegates to Acme::Client::Resources::Challenge' do
expect(challenge.public_send(method)).to eq(value)
diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb
index ee7058baf8d..1a759103c44 100644
--- a/spec/lib/gitlab/lets_encrypt/order_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb
@@ -3,20 +3,13 @@
require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Order do
- delegated_methods = {
- url: 'https://example.com/',
- status: 'valid'
- }
-
- let(:acme_order) do
- acme_order = instance_double('Acme::Client::Resources::Order')
- allow(acme_order).to receive_messages(delegated_methods)
- acme_order
- end
+ include LetsEncryptHelpers
+
+ let(:acme_order) { acme_order_double }
let(:order) { described_class.new(acme_order) }
- delegated_methods.each do |method, value|
+ LetsEncryptHelpers::ACME_ORDER_METHODS.each do |method, value|
describe "##{method}" do
it 'delegates to Acme::Client::Resources::Order' do
expect(order.public_send(method)).to eq(value)
@@ -25,15 +18,24 @@ describe ::Gitlab::LetsEncrypt::Order do
end
describe '#new_challenge' do
- before do
- challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01')
- authorization = instance_double('Acme::Client::Resources::Authorization')
- allow(authorization).to receive(:http).and_return(challenge)
- allow(acme_order).to receive(:authorizations).and_return([authorization])
- end
-
it 'returns challenge' do
expect(order.new_challenge).to be_a(::Gitlab::LetsEncrypt::Challenge)
end
end
+
+ describe '#request_certificate' do
+ let(:private_key) do
+ OpenSSL::PKey::RSA.new(4096).to_pem
+ end
+
+ it 'generates csr and finalizes order' do
+ expect(acme_order).to receive(:finalize) do |csr:|
+ expect do
+ csr.csr # it's being evaluated lazily
+ end.not_to raise_error
+ end
+
+ order.request_certificate(domain: 'example.com', private_key: private_key)
+ end
+ end
end
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index f9c0daf1ef1..32296caf819 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -83,5 +83,13 @@ describe Gitlab::OmniauthInitializer do
subject.execute([cas3_config])
end
+
+ it 'configures name for openid_connect' do
+ openid_connect_config = { 'name' => 'openid_connect', 'args' => {} }
+
+ expect(devise_config).to receive(:omniauth).with(:openid_connect, name: 'openid_connect')
+
+ subject.execute([openid_connect_config])
+ end
end
end
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index fe46c67a920..5f0a7e925ca 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -15,6 +15,13 @@ describe Gitlab::Template::GitlabCiYmlTemplate do
expect(all).to include('Docker')
expect(all).to include('Ruby')
end
+
+ it 'ensure that the template name is used exactly once' do
+ all = subject.all.group_by(&:name)
+ duplicates = all.select { |_, templates| templates.length > 1 }
+
+ expect(duplicates).to be_empty
+ end
end
describe '.find' do
diff --git a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
index afcaefa0591..abf39317188 100644
--- a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
+++ b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
@@ -8,9 +8,13 @@ describe EnqueueVerifyPagesDomainWorkers, :sidekiq, :migration do
end
end
+ let(:domains_table) { table(:pages_domains) }
+
describe '#up' do
it 'enqueues a verification worker for every domain' do
- domains = 1.upto(3).map { |i| PagesDomain.create!(domain: "my#{i}.domain.com") }
+ domains = Array.new(3) do |i|
+ domains_table.create!(domain: "my#{i}.domain.com", verification_code: "123#{i}")
+ end
expect { migrate! }.to change(PagesDomainVerificationWorker.jobs, :size).by(3)
diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb
index 13b8919343e..e8c44c141c3 100644
--- a/spec/migrations/remove_orphaned_label_links_spec.rb
+++ b/spec/migrations/remove_orphaned_label_links_spec.rb
@@ -10,6 +10,12 @@ describe RemoveOrphanedLabelLinks, :migration do
let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let(:label) { create_label }
+ before do
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
+ end
+
context 'add foreign key on label_id' do
let!(:label_link_with_label) { create_label_link(label_id: label.id) }
let!(:label_link_without_label) { create_label_link(label_id: nil) }
diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
new file mode 100644
index 00000000000..54f3e264df0
--- /dev/null
+++ b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb')
+
+describe ScheduleFillValidTimeForPagesDomainCertificates, :migration, :sidekiq do
+ let(:migration_class) { described_class::MIGRATION }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ let(:domains_table) { table(:pages_domains) }
+
+ let(:certificate) do
+ File.read('spec/fixtures/passphrase_x509_certificate.crt')
+ end
+
+ before do
+ domains_table.create!(domain: "domain1.example.com", verification_code: "123")
+ domains_table.create!(domain: "domain2.example.com", verification_code: "123", certificate: '')
+ domains_table.create!(domain: "domain3.example.com", verification_code: "123", certificate: certificate)
+ domains_table.create!(domain: "domain4.example.com", verification_code: "123", certificate: certificate)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ first_id = domains_table.find_by_domain("domain3.example.com").id
+ last_id = domains_table.find_by_domain("domain4.example.com").id
+
+ expect(migration_name).to be_scheduled_delayed_migration(5.minutes, first_id, last_id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+
+ it 'sets certificate valid_not_before/not_after' do
+ perform_enqueued_jobs do
+ migrate!
+
+ domain = domains_table.find_by_domain("domain3.example.com")
+ expect(domain.certificate_valid_not_before)
+ .to eq(Time.parse("2018-03-23 14:02:08 UTC"))
+ expect(domain.certificate_valid_not_after)
+ .to eq(Time.parse("2019-03-23 14:02:08 UTC"))
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 89d18abee27..d98db024f73 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1294,7 +1294,7 @@ describe Ci::Build do
build.cancel!
end
- it { is_expected.not_to be_retryable }
+ it { is_expected.to be_retryable }
end
end
@@ -1824,7 +1824,7 @@ describe Ci::Build do
context 'when build has been canceled' do
subject { build_stubbed(:ci_build, :manual, status: :canceled) }
- it { is_expected.not_to be_playable }
+ it { is_expected.to be_playable }
end
context 'when build is successful' do
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 9a6471557ea..227870eb27f 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -128,7 +128,7 @@ describe Ci::PipelineSchedule do
context 'when pipeline schedule runs every minute' do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) }
- it "updates next_run_at to the sidekiq worker's execution time" do
+ it "updates next_run_at to the sidekiq worker's execution time", :quarantine do
expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at)
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 956c5675f38..fc28c216b21 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1088,22 +1088,6 @@ describe MergeRequest do
end
end
- describe "#reset_auto_merge" do
- let(:merge_if_green) do
- create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user),
- merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" }
- end
-
- it "sets the item to false" do
- merge_if_green.reset_auto_merge
- merge_if_green.reload
-
- expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey
- expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil
- expect(merge_if_green.merge_params["commit_message"]).to be_nil
- end
- end
-
describe '#committers' do
it 'returns all the committers of every commit in the merge request' do
users = subject.commits.without_merge_commits.map(&:committer_email).uniq.map do |email|
diff --git a/spec/models/pages_domain_acme_order_spec.rb b/spec/models/pages_domain_acme_order_spec.rb
new file mode 100644
index 00000000000..4ffb4fc7389
--- /dev/null
+++ b/spec/models/pages_domain_acme_order_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomainAcmeOrder do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '.expired' do
+ let!(:not_expired_order) { create(:pages_domain_acme_order) }
+ let!(:expired_order) { create(:pages_domain_acme_order, :expired) }
+
+ it 'returns only expired orders' do
+ expect(described_class.count).to eq(2)
+ expect(described_class.expired).to eq([expired_order])
+ end
+ end
+
+ describe '.find_by_domain_and_token' do
+ let!(:domain) { create(:pages_domain, domain: 'test.com') }
+ let!(:acme_order) { create(:pages_domain_acme_order, challenge_token: 'righttoken', pages_domain: domain) }
+
+ where(:domain_name, :challenge_token, :present) do
+ 'test.com' | 'righttoken' | true
+ 'test.com' | 'wrongtoken' | false
+ 'test.org' | 'righttoken' | false
+ end
+
+ with_them do
+ subject { described_class.find_by_domain_and_token(domain_name, challenge_token).present? }
+
+ it { is_expected.to eq(present) }
+ end
+ end
+
+ subject { create(:pages_domain_acme_order) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:pages_domain) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:pages_domain) }
+ it { is_expected.to validate_presence_of(:expires_at) }
+ it { is_expected.to validate_presence_of(:url) }
+ it { is_expected.to validate_presence_of(:challenge_token) }
+ it { is_expected.to validate_presence_of(:challenge_file_content) }
+ it { is_expected.to validate_presence_of(:private_key) }
+ end
+end
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index ec4d4517f82..fdc81359d34 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -81,6 +81,17 @@ describe PagesDomain do
end
end
+ describe 'when certificate is specified' do
+ let(:domain) { build(:pages_domain) }
+
+ it 'saves validity time' do
+ domain.save
+
+ expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2016-02-12 14:32:00 UTC"))
+ expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2020-04-12 14:32:00 UTC"))
+ end
+ end
+
describe 'validate certificate' do
subject { domain }
diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb
index 4aa62028169..f596cee81dc 100644
--- a/spec/models/project_ci_cd_setting_spec.rb
+++ b/spec/models/project_ci_cd_setting_spec.rb
@@ -21,4 +21,32 @@ describe ProjectCiCdSetting do
2.times { described_class.available? }
end
end
+
+ describe 'validations' do
+ it 'validates default_git_depth is between 0 and 1000 or nil' do
+ expect(subject).to validate_numericality_of(:default_git_depth)
+ .only_integer
+ .is_greater_than_or_equal_to(0)
+ .is_less_than_or_equal_to(1000)
+ .allow_nil
+ end
+ end
+
+ describe '#default_git_depth' do
+ let(:default_value) { described_class::DEFAULT_GIT_DEPTH }
+
+ it 'sets default value for new records' do
+ project = create(:project)
+
+ expect(project.ci_cd_settings.default_git_depth).to eq(default_value)
+ end
+
+ it 'does not set default value if present' do
+ project = build(:project)
+ project.build_ci_cd_settings(default_git_depth: 0)
+ project.save!
+
+ expect(project.reload.ci_cd_settings.default_git_depth).to eq(0)
+ end
+ end
end
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index 3430111ca9e..9ed8e3a4e0a 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -119,23 +119,23 @@ describe Ci::BuildRunnerPresenter do
end
describe '#git_depth' do
- subject { presenter.git_depth }
-
let(:build) { create(:ci_build) }
- it 'returns the correct git depth' do
- is_expected.to eq(0)
- end
+ subject(:git_depth) { presenter.git_depth }
context 'when GIT_DEPTH variable is specified' do
before do
create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline)
end
- it 'returns the correct git depth' do
- is_expected.to eq(1)
+ it 'returns its value' do
+ expect(git_depth).to eq(1)
end
end
+
+ it 'defaults to git depth setting for the project' do
+ expect(git_depth).to eq(build.project.default_git_depth)
+ end
end
describe '#refspecs' do
@@ -144,24 +144,24 @@ describe Ci::BuildRunnerPresenter do
let(:build) { create(:ci_build) }
it 'returns the correct refspecs' do
- is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
+ is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
end
- context 'when GIT_DEPTH variable is specified' do
- before do
- create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline)
- end
+ context 'when ref is tag' do
+ let(:build) { create(:ci_build, :tag) }
it 'returns the correct refspecs' do
- is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
+ is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}")
end
- context 'when ref is tag' do
- let(:build) { create(:ci_build, :tag) }
+ context 'when GIT_DEPTH is zero' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline)
+ end
it 'returns the correct refspecs' do
- is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}")
+ is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*')
end
end
end
@@ -173,17 +173,27 @@ describe Ci::BuildRunnerPresenter do
it 'returns the correct refspecs' do
is_expected
- .to contain_exactly('+refs/heads/*:refs/remotes/origin/*',
- '+refs/tags/*:refs/tags/*',
- '+refs/merge-requests/1/head:refs/merge-requests/1/head')
+ .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head')
+ end
+
+ context 'when GIT_DEPTH is zero' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline)
+ end
+
+ it 'returns the correct refspecs' do
+ is_expected
+ .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head',
+ '+refs/heads/*:refs/remotes/origin/*',
+ '+refs/tags/*:refs/tags/*')
+ end
end
context 'when pipeline is legacy detached merge request pipeline' do
let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) }
it 'returns the correct refspecs' do
- is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
+ is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
end
end
end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 7208cec357a..89ee6f896f9 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -864,7 +864,7 @@ describe API::Jobs do
end
describe 'POST /projects/:id/jobs/:job_id/retry' do
- let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
before do
post api("/projects/#{project.id}/jobs/#{job.id}/retry", api_user)
@@ -874,7 +874,7 @@ describe API::Jobs do
context 'user with :update_build permission' do
it 'retries non-running job' do
expect(response).to have_gitlab_http_status(201)
- expect(project.builds.first.status).to eq('failed')
+ expect(project.builds.first.status).to eq('canceled')
expect(json_response['status']).to eq('pending')
end
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 3202050ac20..038c958b5cc 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -444,8 +444,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
'sha' => job.sha,
'before_sha' => job.before_sha,
'ref_type' => 'branch',
- 'refspecs' => %w[+refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*],
- 'depth' => 0 }
+ 'refspecs' => ["+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"],
+ 'depth' => project.default_git_depth }
end
let(:expected_steps) do
@@ -531,7 +531,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when GIT_DEPTH is not specified' do
+ context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
+ before do
+ project.update!(default_git_depth: nil)
+ end
+
it 'specifies refspecs' do
request_job
@@ -587,7 +591,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when GIT_DEPTH is not specified' do
+ context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
+ before do
+ project.update!(default_git_depth: nil)
+ end
+
it 'specifies refspecs' do
request_job
diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb
new file mode 100644
index 00000000000..ee2531197b1
--- /dev/null
+++ b/spec/requests/api/task_completion_status_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'task completion status response' do
+ set(:user) { create(:user) }
+ set(:project) do
+ create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ end
+
+ shared_examples 'taskable completion status provider' do |path|
+ samples = [
+ {
+ description: '',
+ expected_count: 0,
+ expected_completed_count: 0
+ },
+ {
+ description: 'Lorem ipsum',
+ expected_count: 0,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [ ] task 1
+ - [x] task 2 },
+ expected_count: 2,
+ expected_completed_count: 1
+ },
+ {
+ description: %{- [ ] task 1
+ - [ ] task 2 },
+ expected_count: 2,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [x] task 1
+ - [x] task 2 },
+ expected_count: 2,
+ expected_completed_count: 2
+ },
+ {
+ description: %{- [ ] task 1},
+ expected_count: 1,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [x] task 1},
+ expected_count: 1,
+ expected_completed_count: 1
+ }
+ ]
+ samples.each do |sample_data|
+ context "with a description of #{sample_data[:description].inspect}" do
+ before do
+ taskable.update!(description: sample_data[:description])
+
+ get api("#{path}?iids[]=#{taskable.iid}", user)
+ end
+
+ it { expect(response).to have_gitlab_http_status(200) }
+
+ it 'returns the expected results' do
+ expect(json_response).to be_an Array
+ expect(json_response).not_to be_empty
+
+ task_completion_status = json_response.first['task_completion_status']
+ expect(task_completion_status['count']).to eq(sample_data[:expected_count])
+ expect(task_completion_status['completed_count']).to eq(sample_data[:expected_completed_count])
+ end
+ end
+ end
+ end
+
+ context 'task list completion status for issues' do
+ it_behaves_like 'taskable completion status provider', '/issues' do
+ let(:taskable) { create(:issue, project: project, author: user) }
+ end
+ end
+
+ context 'task list completion status for merge_requests' do
+ it_behaves_like 'taskable completion status provider', '/merge_requests' do
+ let(:taskable) { create(:merge_request, source_project: project, target_project: project, author: user) }
+ end
+ end
+end
diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb
new file mode 100644
index 00000000000..197fa16961d
--- /dev/null
+++ b/spec/services/auto_merge/base_service_spec.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AutoMerge::BaseService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user, params) }
+ let(:merge_request) { create(:merge_request) }
+ let(:params) { {} }
+
+ describe '#execute' do
+ subject { service.execute(merge_request) }
+
+ it 'sets properies to the merge request' do
+ subject
+
+ merge_request.reload
+ expect(merge_request).to be_auto_merge_enabled
+ expect(merge_request.merge_user).to eq(user)
+ expect(merge_request.auto_merge_strategy).to eq('base')
+ end
+
+ it 'yields block' do
+ expect { |b| service.execute(merge_request, &b) }.to yield_control.once
+ end
+
+ it 'returns activated strategy name' do
+ is_expected.to eq(:base)
+ end
+
+ context 'when merge parameters are given' do
+ let(:params) do
+ {
+ 'commit_message' => "Merge branch 'patch-12' into 'master'",
+ 'sha' => "200fcc9c260f7219eaf0daba87d818f0922c5b18",
+ 'should_remove_source_branch' => false,
+ 'squash' => false,
+ 'squash_commit_message' => "Update README.md"
+ }
+ end
+
+ it 'sets merge parameters' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'")
+ expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18')
+ expect(merge_request.merge_params['should_remove_source_branch']).to eq(false)
+ expect(merge_request.merge_params['squash']).to eq(false)
+ expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md')
+ end
+ end
+
+ context 'when strategy is merge when pipeline succeeds' do
+ let(:service) { AutoMerge::MergeWhenPipelineSucceedsService.new(project, user) }
+
+ it 'sets the auto merge strategy' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ end
+
+ it 'returns activated strategy name' do
+ is_expected.to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS.to_sym)
+ end
+ end
+
+ context 'when failed to save' do
+ before do
+ allow(merge_request).to receive(:save) { false }
+ end
+
+ it 'does not yield block' do
+ expect { |b| service.execute(merge_request, &b) }.not_to yield_control
+ end
+
+ it 'returns failed' do
+ is_expected.to eq(:failed)
+ end
+ end
+ end
+
+ describe '#cancel' do
+ subject { service.cancel(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'removes properies from the merge request' do
+ subject
+
+ merge_request.reload
+ expect(merge_request).not_to be_auto_merge_enabled
+ expect(merge_request.merge_user).to be_nil
+ expect(merge_request.auto_merge_strategy).to be_nil
+ end
+
+ it 'yields block' do
+ expect { |b| service.cancel(merge_request, &b) }.to yield_control.once
+ end
+
+ it 'returns success status' do
+ expect(subject[:status]).to eq(:success)
+ end
+
+ context 'when merge params are set' do
+ before do
+ merge_request.update!(merge_params:
+ {
+ 'should_remove_source_branch' => false,
+ 'commit_message' => "Merge branch 'patch-12' into 'master'",
+ 'squash_commit_message' => "Update README.md",
+ 'auto_merge_strategy' => 'merge_when_pipeline_succeeds'
+ })
+ end
+
+ it 'removes merge parameters' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.merge_params['should_remove_source_branch']).to be_nil
+ expect(merge_request.merge_params['commit_message']).to be_nil
+ expect(merge_request.merge_params['squash_commit_message']).to be_nil
+ expect(merge_request.merge_params['auto_merge_strategy']).to be_nil
+ end
+ end
+
+ context 'when failed to save' do
+ before do
+ allow(merge_request).to receive(:save) { false }
+ end
+
+ it 'does not yield block' do
+ expect { |b| service.execute(merge_request, &b) }.not_to yield_control
+ end
+
+ it 'returns error status' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq("Can't cancel the automatic merge")
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb
new file mode 100644
index 00000000000..9479439bde8
--- /dev/null
+++ b/spec/services/merge_requests/create_pipeline_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::CreatePipelineService do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+ let(:service) { described_class.new(project, user, params) }
+ let(:params) { {} }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
+ subject { service.execute(merge_request) }
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ let(:config) do
+ { rspec: { script: 'echo', only: ['merge_requests'] } }
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_branch: 'feature',
+ source_project: project,
+ target_branch: 'master',
+ target_project: project)
+ end
+
+ it 'creates a detached merge request pipeline' do
+ expect { subject }.to change { Ci::Pipeline.count }.by(1)
+
+ expect(subject).to be_persisted
+ expect(subject).to be_detached_merge_request_pipeline
+ end
+
+ context 'when service is called multiple times' do
+ it 'creates a pipeline once' do
+ expect do
+ service.execute(merge_request)
+ service.execute(merge_request)
+ end.to change { Ci::Pipeline.count }.by(1)
+ end
+
+ context 'when allow_duplicate option is true' do
+ let(:params) { { allow_duplicate: true } }
+
+ it 'creates pipelines multiple times' do
+ expect do
+ service.execute(merge_request)
+ service.execute(merge_request)
+ end.to change { Ci::Pipeline.count }.by(2)
+ end
+ end
+ end
+
+ context 'when .gitlab-ci.yml does not have only: [merge_requests] keyword' do
+ let(:config) do
+ { rspec: { script: 'echo' } }
+ end
+
+ it 'does not create a pipeline' do
+ expect { subject }.not_to change { Ci::Pipeline.count }
+ end
+ end
+ end
+end
diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb
new file mode 100644
index 00000000000..d59aa9b979e
--- /dev/null
+++ b/spec/services/pages_domains/create_acme_order_service_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomains::CreateAcmeOrderService do
+ include LetsEncryptHelpers
+
+ let(:pages_domain) { create(:pages_domain) }
+
+ let(:challenge) { ::Gitlab::LetsEncrypt::Challenge.new(acme_challenge_double) }
+
+ let(:order_double) do
+ Gitlab::LetsEncrypt::Order.new(acme_order_double).tap do |order|
+ allow(order).to receive(:new_challenge).and_return(challenge)
+ end
+ end
+
+ let(:lets_encrypt_client) do
+ instance_double('Gitlab::LetsEncrypt::Client').tap do |client|
+ allow(client).to receive(:new_order).with(pages_domain.domain)
+ .and_return(order_double)
+ end
+ end
+
+ let(:service) { described_class.new(pages_domain) }
+
+ before do
+ allow(::Gitlab::LetsEncrypt::Client).to receive(:new).and_return(lets_encrypt_client)
+ end
+
+ it 'saves order to database before requesting validation' do
+ allow(pages_domain.acme_orders).to receive(:create!).and_call_original
+ allow(challenge).to receive(:request_validation).and_call_original
+
+ service.execute
+
+ expect(pages_domain.acme_orders).to have_received(:create!).ordered
+ expect(challenge).to have_received(:request_validation).ordered
+ end
+
+ it 'generates and saves private key' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect { OpenSSL::PKey::RSA.new(saved_order.private_key) }.not_to raise_error
+ end
+
+ it 'properly saves order attributes' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect(saved_order.url).to eq(order_double.url)
+ expect(saved_order.expires_at).to be_like_time(order_double.expires)
+ end
+
+ it 'properly saves challenge attributes' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect(saved_order.challenge_token).to eq(challenge.token)
+ expect(saved_order.challenge_file_content).to eq(challenge.file_content)
+ end
+end
diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
new file mode 100644
index 00000000000..6d7be27939c
--- /dev/null
+++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomains::ObtainLetsEncryptCertificateService do
+ include LetsEncryptHelpers
+
+ let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key) }
+ let(:service) { described_class.new(pages_domain) }
+
+ before do
+ stub_lets_encrypt_settings
+ end
+
+ def expect_to_create_acme_challenge
+ expect(::PagesDomains::CreateAcmeOrderService).to receive(:new).with(pages_domain)
+ .and_wrap_original do |m, *args|
+ create_service = m.call(*args)
+
+ expect(create_service).to receive(:execute)
+
+ create_service
+ end
+ end
+
+ def stub_lets_encrypt_order(url, status)
+ order = ::Gitlab::LetsEncrypt::Order.new(acme_order_double(status: status))
+
+ allow_any_instance_of(::Gitlab::LetsEncrypt::Client).to(
+ receive(:load_order).with(url).and_return(order)
+ )
+
+ order
+ end
+
+ context 'when there is no acme order' do
+ it 'creates acme order' do
+ expect_to_create_acme_challenge
+
+ service.execute
+ end
+ end
+
+ context 'when there is expired acme order' do
+ let!(:existing_order) do
+ create(:pages_domain_acme_order, :expired, pages_domain: pages_domain)
+ end
+
+ it 'removes acme order and creates new one' do
+ expect_to_create_acme_challenge
+
+ service.execute
+
+ expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil
+ end
+ end
+
+ %w(pending processing).each do |status|
+ context "there is an order in '#{status}' status" do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ before do
+ stub_lets_encrypt_order(existing_order.url, status)
+ end
+
+ it 'does not raise errors' do
+ expect do
+ service.execute
+ end.not_to raise_error
+ end
+ end
+ end
+
+ context 'when order is ready' do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ let!(:api_order) do
+ stub_lets_encrypt_order(existing_order.url, 'ready')
+ end
+
+ it 'request certificate' do
+ expect(api_order).to receive(:request_certificate).and_call_original
+
+ service.execute
+ end
+ end
+
+ context 'when order is valid' do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ let!(:api_order) do
+ stub_lets_encrypt_order(existing_order.url, 'valid')
+ end
+
+ let(:certificate) do
+ key = OpenSSL::PKey.read(existing_order.private_key)
+
+ subject = "/C=BE/O=Test/OU=Test/CN=#{pages_domain.domain}"
+
+ cert = OpenSSL::X509::Certificate.new
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
+ cert.not_before = Time.now
+ cert.not_after = 1.year.from_now
+ cert.public_key = key.public_key
+ cert.serial = 0x0
+ cert.version = 2
+
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = cert
+ ef.issuer_certificate = cert
+ cert.extensions = [
+ ef.create_extension("basicConstraints", "CA:TRUE", true),
+ ef.create_extension("subjectKeyIdentifier", "hash")
+ ]
+ cert.add_extension ef.create_extension("authorityKeyIdentifier",
+ "keyid:always,issuer:always")
+
+ cert.sign key, OpenSSL::Digest::SHA1.new
+
+ cert.to_pem
+ end
+
+ before do
+ expect(api_order).to receive(:certificate) { certificate }
+ end
+
+ it 'saves private_key and certificate for domain' do
+ service.execute
+
+ expect(pages_domain.key).to be_present
+ expect(pages_domain.certificate).to eq(certificate)
+ end
+
+ it 'removes order from database' do
+ service.execute
+
+ expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil
+ end
+ end
+end
diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb
index f7261cd7125..d25e9958831 100644
--- a/spec/services/preview_markdown_service_spec.rb
+++ b/spec/services/preview_markdown_service_spec.rb
@@ -56,7 +56,9 @@ describe PreviewMarkdownService do
expect(Gitlab::Diff::SuggestionsParser)
.to receive(:parse)
- .with(text, position: position, project: merge_request.project)
+ .with(text, position: position,
+ project: merge_request.project,
+ supports_suggestion: true)
.and_call_original
result = service.execute
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index ec3f1782e8f..3211a6e1310 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -145,6 +145,30 @@ describe Projects::ForkService do
end
end
+ context "CI/CD settings" do
+ let(:to_project) { fork_project(@from_project, @to_user) }
+
+ context "when origin has git depth specified" do
+ before do
+ @from_project.update(default_git_depth: 42)
+ end
+
+ it "inherits default_git_depth from the origin project" do
+ expect(to_project.default_git_depth).to eq(42)
+ end
+ end
+
+ context "when origin does not define git depth" do
+ before do
+ @from_project.update!(default_git_depth: nil)
+ end
+
+ it "the fork has git depth set to 0" do
+ expect(to_project.default_git_depth).to eq(0)
+ end
+ end
+ end
+
context "when project has restricted visibility level" do
context "and only one visibility level is restricted" do
before do
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index ccd44e615a8..d95f9e3349b 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -96,7 +96,7 @@ describe Suggestions::CreateService do
it 'creates no suggestion when diff file is not found' do
expect_next_instance_of(DiffNote) do |diff_note|
- expect(diff_note).to receive(:latest_diff_file).twice { nil }
+ expect(diff_note).to receive(:latest_diff_file).once { nil }
end
expect { subject.execute }.not_to change(Suggestion, :count)
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 89dfbf931d2..5d5a0a7b5d2 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -20,7 +20,7 @@ shared_examples 'reportable note' do |type|
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
- expect(dropdown).to have_link('Report abuse to GitLab', href: abuse_report_path)
+ expect(dropdown).to have_link('Report abuse to admin', href: abuse_report_path)
if type == 'issue' || type == 'merge_request'
expect(dropdown).to have_button('Delete comment')
@@ -33,7 +33,7 @@ shared_examples 'reportable note' do |type|
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
- dropdown.click_link('Report abuse to GitLab')
+ dropdown.click_link('Report abuse to admin')
expect(find('#user_name')['value']).to match(note.author.username)
expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note))
diff --git a/spec/support/helpers/lets_encrypt_helpers.rb b/spec/support/helpers/lets_encrypt_helpers.rb
index 7f0886b451c..2857416ad95 100644
--- a/spec/support/helpers/lets_encrypt_helpers.rb
+++ b/spec/support/helpers/lets_encrypt_helpers.rb
@@ -1,6 +1,26 @@
# frozen_string_literal: true
module LetsEncryptHelpers
+ ACME_ORDER_METHODS = {
+ url: 'https://example.com/',
+ status: 'valid',
+ expires: 2.days.from_now
+ }.freeze
+
+ ACME_CHALLENGE_METHODS = {
+ status: 'pending',
+ token: 'tokenvalue',
+ file_content: 'hereisfilecontent',
+ request_validation: true
+ }.freeze
+
+ def stub_lets_encrypt_settings
+ stub_application_setting(
+ lets_encrypt_notification_email: 'myemail@test.example.com',
+ lets_encrypt_terms_of_service_accepted: true
+ )
+ end
+
def stub_lets_encrypt_client
client = instance_double('Acme::Client')
@@ -16,4 +36,24 @@ module LetsEncryptHelpers
client
end
+
+ def acme_challenge_double
+ challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01')
+ allow(challenge).to receive_messages(ACME_CHALLENGE_METHODS)
+ challenge
+ end
+
+ def acme_authorization_double
+ authorization = instance_double('Acme::Client::Resources::Authorization')
+ allow(authorization).to receive(:http).and_return(acme_challenge_double)
+ authorization
+ end
+
+ def acme_order_double(attributes = {})
+ acme_order = instance_double('Acme::Client::Resources::Order')
+ allow(acme_order).to receive_messages(ACME_ORDER_METHODS.merge(attributes))
+ allow(acme_order).to receive(:authorizations).and_return([acme_authorization_double])
+ allow(acme_order).to receive(:finalize)
+ acme_order
+ end
end
diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index 54d9f5b15f2..54d9f5b15f2 100644
--- a/spec/support/shared_context/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
index 982e0317f7f..b7080c68270 100644
--- a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
+++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
@@ -9,87 +9,87 @@
# - `filepath`: path of the file (contains filename)
# - `subject`: the request to be made to the controller. Example:
# subject { get :show, namespace_id: project.namespace, project_id: project }
-shared_examples 'repository lfs file load' do
- context 'when file is stored in lfs' do
- let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
- let(:lfs_size) { '1575078' }
- let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
+shared_examples 'a controller that can serve LFS files' do
+ let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
+ let(:lfs_size) { '1575078' }
+ let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
+
+ context 'when lfs is enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ end
- context 'when lfs is enabled' do
+ context 'when project has access' do
before do
- allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ project.lfs_objects << lfs_object
+ allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
+ allow(controller).to receive(:send_file) { controller.head :ok }
end
- context 'when project has access' do
- before do
- project.lfs_objects << lfs_object
- allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
- allow(controller).to receive(:send_file) { controller.head :ok }
- end
+ it 'serves the file' do
+ lfs_uploader = LfsObjectUploader.new(lfs_object)
- it 'serves the file' do
- # Notice the filename= is omitted from the disposition; this is because
- # Rails 5 will append this header in send_file
- expect(controller).to receive(:send_file)
- .with(
- "#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897",
- filename: filename,
- disposition: %Q(attachment; filename*=UTF-8''#{filename}))
+ # Notice the filename= is omitted from the disposition; this is because
+ # Rails 5 will append this header in send_file
+ expect(controller).to receive(:send_file)
+ .with(
+ File.join(lfs_uploader.root, lfs_uploader.store_dir, lfs_uploader.filename),
+ filename: filename,
+ disposition: %Q(attachment; filename*=UTF-8''#{filename}))
- subject
+ subject
- expect(response).to have_gitlab_http_status(200)
- end
+ expect(response).to have_gitlab_http_status(200)
+ end
- context 'and lfs uses object storage' do
- let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
+ context 'and lfs uses object storage' do
+ let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
- before do
- stub_lfs_object_storage
- lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
- end
+ before do
+ stub_lfs_object_storage
+ lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+ end
- it 'responds with redirect to file' do
- subject
+ it 'responds with redirect to file' do
+ subject
- expect(response).to have_gitlab_http_status(302)
- expect(response.location).to include(lfs_object.reload.file.path)
- end
+ expect(response).to have_gitlab_http_status(302)
+ expect(response.location).to include(lfs_object.reload.file.path)
+ end
- it 'sets content disposition' do
- subject
+ it 'sets content disposition' do
+ subject
- file_uri = URI.parse(response.location)
- params = CGI.parse(file_uri.query)
+ file_uri = URI.parse(response.location)
+ params = CGI.parse(file_uri.query)
- expect(params["response-content-disposition"].first).to eq(%q(attachment; filename="lfs_object.iso"; filename*=UTF-8''lfs_object.iso))
- end
+ expect(params["response-content-disposition"].first).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename}))
end
end
+ end
- context 'when project does not have access' do
- it 'does not serve the file' do
- subject
+ context 'when project does not have access' do
+ it 'does not serve the file' do
+ subject
- expect(response).to have_gitlab_http_status(404)
- end
+ expect(response).to have_gitlab_http_status(404)
end
end
+ end
- context 'when lfs is not enabled' do
- before do
- allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
- end
+ context 'when lfs is not enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
+ end
- it 'delivers ASCII file' do
- subject
+ it 'delivers ASCII file' do
+ subject
- expect(response).to have_gitlab_http_status(200)
- expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(response.header['Content-Disposition'])
- .to eq('inline')
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
- end
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.header['Content-Disposition'])
+ .to eq('inline')
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
end
diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
index 8a9ab02eaca..ae47f364296 100644
--- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
@@ -12,10 +12,10 @@ describe 'projects/notes/_more_actions_dropdown' do
assign(:project, project)
end
- it 'shows Report abuse to GitLab button if not editable and not current users comment' do
+ it 'shows Report abuse to admin button if not editable and not current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: false, note: note
- expect(rendered).to have_link('Report abuse to GitLab')
+ expect(rendered).to have_link('Report abuse to admin')
end
it 'does not show the More actions button if not editable and current users comment' do
@@ -24,10 +24,10 @@ describe 'projects/notes/_more_actions_dropdown' do
expect(rendered).not_to have_selector('.dropdown.more-actions')
end
- it 'shows Report abuse to GitLab and Delete buttons if editable and not current users comment' do
+ it 'shows Report abuse to admin and Delete buttons if editable and not current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: true, note: note
- expect(rendered).to have_link('Report abuse to GitLab')
+ expect(rendered).to have_link('Report abuse to admin')
expect(rendered).to have_link('Delete comment')
end