summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml30
-rw-r--r--.gitlab/issue_templates/Feature proposal.md8
-rw-r--r--.gitlab/issue_templates/Security Release.md69
-rw-r--r--.rubocop_todo.yml11
-rw-r--r--CHANGELOG.md18
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--Gemfile14
-rw-r--r--Gemfile.lock47
-rw-r--r--app/assets/javascripts/badges/components/badge_list_row.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue2
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.vue4
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue2
-rw-r--r--app/assets/javascripts/contextual_sidebar.js2
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue4
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions_dropdown.vue14
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussions.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue2
-rw-r--r--app/assets/javascripts/diffs/components/image_diff_overlay.vue4
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue6
-rw-r--r--app/assets/javascripts/diffs/constants.js2
-rw-r--r--app/assets/javascripts/diffs/store/actions.js15
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js9
-rw-r--r--app/assets/javascripts/diffs/store/utils.js63
-rw-r--r--app/assets/javascripts/diffs/workers/tree_worker.js14
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue2
-rw-r--r--app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue4
-rw-r--r--app/assets/javascripts/frequent_items/components/app.vue6
-rw-r--r--app/assets/javascripts/frequent_items/index.js6
-rw-r--r--app/assets/javascripts/ide/components/activity_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/message_field.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue2
-rw-r--r--app/assets/javascripts/ide/components/editor_mode_dropdown.vue4
-rw-r--r--app/assets/javascripts/ide/components/file_finder/index.vue6
-rw-r--r--app/assets/javascripts/ide/components/file_templates/dropdown.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree.vue4
-rw-r--r--app/assets/javascripts/ide/components/jobs/detail.vue2
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue4
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue10
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue2
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue4
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue4
-rw-r--r--app/assets/javascripts/ide/components/resizable_panel.vue4
-rw-r--r--app/assets/javascripts/ide/components/shared/tokened_input.vue4
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue6
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue14
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue14
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue4
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue4
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue2
-rw-r--r--app/assets/javascripts/reports/components/modal_open_name.vue2
-rw-r--r--app/assets/javascripts/reports/components/test_issue_body.vue2
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/bar_chart.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_modal.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/navigation_tabs.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue8
-rw-r--r--app/assets/javascripts/vuex_shared/modules/modal/actions.js3
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss9
-rw-r--r--app/assets/stylesheets/pages/boards.scss15
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss111
-rw-r--r--app/assets/stylesheets/pages/projects.scss6
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/dashboard/projects_controller.rb3
-rw-r--r--app/controllers/explore/projects_controller.rb3
-rw-r--r--app/controllers/import/base_controller.rb2
-rw-r--r--app/controllers/import/github_controller.rb33
-rw-r--r--app/controllers/projects/badges_controller.rb13
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/milestones_controller.rb2
-rw-r--r--app/finders/milestones_finder.rb12
-rw-r--r--app/models/ci/build.rb9
-rw-r--r--app/models/clusters/applications/prometheus.rb27
-rw-r--r--app/models/clusters/concerns/application_status.rb4
-rw-r--r--app/models/concerns/cache_markdown_field.rb2
-rw-r--r--app/models/external_issue.rb8
-rw-r--r--app/models/milestone.rb7
-rw-r--r--app/models/project.rb45
-rw-r--r--app/models/remote_mirror.rb12
-rw-r--r--app/serializers/base_serializer.rb10
-rw-r--r--app/serializers/merge_request_diff_entity.rb8
-rw-r--r--app/services/clusters/applications/base_helm_service.rb4
-rw-r--r--app/services/import/base_service.rb35
-rw-r--r--app/services/import/github_service.rb48
-rw-r--r--app/services/issuable_base_service.rb4
-rw-r--r--app/services/milestones/promote_service.rb4
-rw-r--r--app/services/projects/autocomplete_service.rb2
-rw-r--r--app/services/projects/protect_default_branch_service.rb67
-rw-r--r--app/views/clusters/clusters/_integration_form.html.haml20
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml6
-rw-r--r--app/views/profiles/accounts/show.html.haml34
-rw-r--r--app/views/projects/artifacts/_tree_file.html.haml2
-rw-r--r--app/views/projects/badges/badge_flat-square.svg.erb17
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/branches/_panel.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml2
-rw-r--r--app/views/search/_category.html.haml26
-rw-r--r--app/views/search/_filter.html.haml20
-rw-r--r--app/views/search/_form.html.haml6
-rw-r--r--app/views/search/_results.html.haml6
-rw-r--r--app/views/search/results/_empty.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml4
-rw-r--r--app/views/search/results/_note.html.haml8
-rw-r--r--app/views/search/results/_snippet_blob.html.haml4
-rw-r--r--app/views/search/results/_snippet_title.html.haml2
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--app/views/shared/_mini_pipeline_graph.html.haml1
-rw-r--r--app/views/shared/_personal_access_tokens_created_container.html.haml2
-rw-r--r--app/views/shared/_personal_access_tokens_form.html.haml4
-rw-r--r--app/views/shared/_personal_access_tokens_table.html.haml2
-rw-r--r--app/views/shared/boards/components/_board.html.haml2
-rw-r--r--app/views/shared/empty_states/_issues.html.haml2
-rw-r--r--app/views/shared/tokens/_scopes_form.html.haml2
-rw-r--r--app/workers/remote_mirror_notification_worker.rb3
-rwxr-xr-xbin/changelog10
-rw-r--r--changelogs/unreleased/26375-markdown-footnotes-not-working.yml5
-rw-r--r--changelogs/unreleased/30120-add-flat-square-badge-style.yml5
-rw-r--r--changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml5
-rw-r--r--changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml5
-rw-r--r--changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml5
-rw-r--r--changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml5
-rw-r--r--changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml5
-rw-r--r--changelogs/unreleased/55945-suggested-change-highlight.yml5
-rw-r--r--changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml6
-rw-r--r--changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml5
-rw-r--r--changelogs/unreleased/ac-pages-subgroups.yml5
-rw-r--r--changelogs/unreleased/add-badge-count-to-projects-and-groups.yml5
-rw-r--r--changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml5
-rw-r--r--changelogs/unreleased/an-gilab-process-name.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-factory.yml5
-rw-r--r--changelogs/unreleased/api-wiki-dot-slug.yml5
-rw-r--r--changelogs/unreleased/auto-devops-custom-domains.yml5
-rw-r--r--changelogs/unreleased/backup_restore_fix_issue_46891.yml5
-rw-r--r--changelogs/unreleased/cleanup-leagcy-artifact-migration.yml5
-rw-r--r--changelogs/unreleased/diff-tree-collapse-directories.yml5
-rw-r--r--changelogs/unreleased/features-document-graphicsmagick-source-installation.yml5
-rw-r--r--changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml5
-rw-r--r--changelogs/unreleased/fix-runner-eternal-loop-when-update-job-result.yml5
-rw-r--r--changelogs/unreleased/gitaly-update-1-13-0.yml5
-rw-r--r--changelogs/unreleased/homepage-proj-descr-cutoff.yml5
-rw-r--r--changelogs/unreleased/security-2770-verify-bundle-import-files.yml5
-rw-r--r--changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml5
-rw-r--r--changelogs/unreleased/sh-fix-gon-helper-avatar.yml5
-rw-r--r--changelogs/unreleased/sh-fix-real-size-warnings.yml5
-rw-r--r--changelogs/unreleased/sh-fix-request-profiles-html.yml5
-rw-r--r--changelogs/unreleased/shared_with_group_path.yml5
-rw-r--r--changelogs/unreleased/suggestion-dashes.yml5
-rw-r--r--changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml5
-rw-r--r--changelogs/unreleased/update-sidekiq-cron.yml6
-rw-r--r--changelogs/unreleased/zj-feature-gate-set-project-path.yml5
-rw-r--r--config/initializers/sentry.rb2
-rw-r--r--config/initializers/tracing.rb13
-rw-r--r--db/migrate/20190104182041_cleanup_legacy_artifact_migration.rb34
-rw-r--r--db/migrate/20190108192941_remove_partial_index_from_ci_builds_artifacts_file.rb18
-rw-r--r--db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb11
-rw-r--r--db/schema.rb4
-rw-r--r--doc/api/avatar.md38
-rw-r--r--doc/api/award_emoji.md197
-rw-r--r--doc/api/branches.md209
-rw-r--r--doc/api/features.md5
-rw-r--r--doc/api/groups.md2
-rw-r--r--doc/api/import.md33
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/ci/docker/using_docker_build.md6
-rw-r--r--doc/ci/yaml/README.md554
-rw-r--r--doc/development/contributing/issue_workflow.md16
-rw-r--r--doc/development/documentation/styleguide.md16
-rw-r--r--doc/development/testing_guide/flaky_tests.md12
-rw-r--r--doc/development/testing_guide/review_apps.md16
-rw-r--r--doc/install/installation.md7
-rw-r--r--doc/topics/autodevops/index.md2
-rw-r--r--doc/user/award_emojis.md32
-rw-r--r--doc/user/discussions/index.md2
-rw-r--r--doc/user/group/subgroups/index.md8
-rw-r--r--doc/user/project/integrations/prometheus_library/index.md3
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md26
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md58
-rw-r--r--doc/user/project/merge_requests/allow_collaboration.md66
-rw-r--r--doc/user/project/merge_requests/img/allow_collaboration.pngbin21522 -> 11028 bytes
-rw-r--r--doc/user/project/merge_requests/img/allow_collaboration_after_save.pngbin0 -> 5415 bytes
-rw-r--r--doc/user/project/merge_requests/img/checkout_button.pngbin0 -> 5977 bytes
-rw-r--r--doc/user/project/merge_requests/index.md9
-rw-r--r--doc/user/project/pages/getting_started_part_one.md10
-rw-r--r--doc/user/project/pages/introduction.md5
-rw-r--r--doc/user/project/pipelines/schedules.md2
-rw-r--r--doc/user/project/pipelines/settings.md22
-rw-r--r--doc/user/project/repository/branches/index.md16
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb5
-rw-r--r--lib/api/features.rb15
-rw-r--r--lib/api/import_github.rb46
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/wikis.rb6
-rw-r--r--lib/backup/files.rb10
-rw-r--r--lib/banzai/filter/footnote_filter.rb68
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb4
-rw-r--r--lib/banzai/filter/sanitization_filter.rb29
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/feature.rb38
-rw-r--r--lib/gitlab.rb8
-rw-r--r--lib/gitlab/access/branch_protection.rb42
-rw-r--r--lib/gitlab/auth/ldap/person.rb4
-rw-r--r--lib/gitlab/background_migration/backfill_project_repositories.rb14
-rw-r--r--lib/gitlab/ci/cron_parser.rb7
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml10
-rw-r--r--lib/gitlab/git/bundle_file.rb30
-rw-r--r--lib/gitlab/git/repository.rb5
-rw-r--r--lib/gitlab/middleware/read_only/controller.rb18
-rw-r--r--lib/gitlab/sentry.rb8
-rw-r--r--lib/gitlab/tracing.rb17
-rw-r--r--lib/gitlab/tracing/factory.rb61
-rw-r--r--lib/gitlab/tracing/jaeger_factory.rb97
-rw-r--r--lib/system_check/base_check.rb8
-rw-r--r--locale/gitlab.pot93
-rw-r--r--package.json2
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock8
-rw-r--r--qa/Rakefile6
-rw-r--r--qa/qa.rb9
-rw-r--r--qa/qa/git/repository.rb13
-rw-r--r--qa/qa/page/profile/personal_access_tokens.rb38
-rw-r--r--qa/qa/page/project/branches/show.rb62
-rw-r--r--qa/qa/page/project/menu.rb31
-rw-r--r--qa/qa/page/project/sub_menus/common.rb23
-rw-r--r--qa/qa/page/project/sub_menus/repository.rb44
-rw-r--r--qa/qa/resource/repository/push.rb2
-rw-r--r--qa/qa/runtime/env.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb92
-rw-r--r--qa/qa/tools/revoke_all_personal_access_tokens.rb44
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb10
-rw-r--r--rubocop/spec_helpers.rb11
-rwxr-xr-xscripts/review_apps/review-apps.sh1
-rw-r--r--spec/controllers/application_controller_spec.rb2
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb41
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb14
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb1
-rw-r--r--spec/controllers/projects/pages_controller_spec.rb4
-rw-r--r--spec/factories/wiki_pages.rb2
-rw-r--r--spec/features/boards/boards_spec.rb6
-rw-r--r--spec/features/dashboard/projects_spec.rb3
-rw-r--r--spec/features/merge_request/user_merges_immediately_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb46
-rw-r--r--spec/features/projects/artifacts/user_browses_artifacts_spec.rb4
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb7
-rw-r--r--spec/fixtures/malicious.bundle1
-rw-r--r--spec/javascripts/diffs/components/compare_versions_dropdown_spec.js33
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js6
-rw-r--r--spec/javascripts/diffs/mock_data/merge_request_diffs.js52
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js22
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js119
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js54
-rw-r--r--spec/lib/banzai/filter/footnote_filter_spec.rb48
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb45
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb32
-rw-r--r--spec/lib/feature_spec.rb14
-rw-r--r--spec/lib/gitlab/access/branch_protection_spec.rb54
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb18
-rw-r--r--spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/delete_diff_files_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb2
-rw-r--r--spec/lib/gitlab/git/bundle_file_spec.rb26
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb17
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb34
-rw-r--r--spec/lib/gitlab/tracing/factory_spec.rb43
-rw-r--r--spec/lib/gitlab/tracing/jaeger_factory_spec.rb71
-rw-r--r--spec/migrations/add_foreign_keys_to_todos_spec.rb6
-rw-r--r--spec/migrations/cleanup_legacy_artifact_migration_spec.rb52
-rw-r--r--spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb12
-rw-r--r--spec/models/ci/build_spec.rb18
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb97
-rw-r--r--spec/models/milestone_spec.rb4
-rw-r--r--spec/models/project_spec.rb80
-rw-r--r--spec/models/remote_mirror_spec.rb19
-rw-r--r--spec/requests/api/features_spec.rb34
-rw-r--r--spec/requests/api/import_github_spec.rb56
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/settings_spec.rb4
-rw-r--r--spec/requests/api/wikis_spec.rb2
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb35
-rw-r--r--spec/services/projects/protect_default_branch_service_spec.rb242
-rw-r--r--spec/sidekiq/cron/job_gem_dependency_spec.rb4
-rw-r--r--spec/spec_helper.rb7
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb4
-rw-r--r--spec/workers/remote_mirror_notification_worker_spec.rb39
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb7
-rw-r--r--yarn.lock8
332 files changed, 4344 insertions, 1251 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6cbc8e21c76..099a3bb5259 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -434,12 +434,12 @@ setup-test-env:
cache: {}
dependencies: []
environment: &review-environment
- name: review/${CI_COMMIT_REF_NAME}
- url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
+ name: review/mr-${CI_MERGE_REQUEST_IID}
+ url: https://gitlab-mr-${CI_MERGE_REQUEST_IID}.${REVIEW_APPS_DOMAIN}
only:
refs:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
+ - merge_requests@gitlab-org/gitlab-ce
+ - merge_requests@gitlab-org/gitlab-ee
kubernetes: active
except:
refs:
@@ -499,6 +499,22 @@ rspec-mysql:
<<: *rspec-metadata-mysql
parallel: 50
+.rspec-quarantine: &rspec-quarantine
+ script:
+ - export CACHE_CLASSES=true
+ - scripts/gitaly-test-spawn
+ - bin/rspec --color --format documentation --tag quarantine spec/
+
+rspec-pg-quarantine:
+ <<: *rspec-metadata-pg
+ <<: *rspec-quarantine
+ allow_failure: true
+
+rspec-mysql-quarantine:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-quarantine
+ allow_failure: true
+
static-analysis:
<<: *dedicated-no-docs-no-db-pull-cache-job
dependencies:
@@ -788,6 +804,7 @@ qa:selectors:
- bundle exec bin/qa Test::Sanity::Selectors
.qa-frontend-node: &qa-frontend-node
+ <<: *dedicated-no-docs-no-db-pull-cache-job
stage: test
variables:
NODE_OPTIONS: --max_old_space_size=3584
@@ -802,7 +819,6 @@ qa:selectors:
- yarn install --frozen-lockfile --cache-folder .yarn-cache
- date
- yarn run webpack-prod
- <<: *except-docs
qa-frontend-node:8:
<<: *qa-frontend-node
@@ -937,8 +953,8 @@ review-deploy:
allow_failure: true
variables:
GIT_DEPTH: "1"
- HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
- DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
+ HOST_SUFFIX: "mr-${CI_MERGE_REQUEST_IID}"
+ DOMAIN: "-mr-${CI_MERGE_REQUEST_IID}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "master"
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
environment:
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index 639a236631d..4d4d3bfda15 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -4,7 +4,7 @@
### Target audience
-<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst" -->
+<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst". Use the persona labels as well https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A -->
### Further details
@@ -12,12 +12,12 @@
### Proposal
-<!--- How are we going to solve the problem? -->
+<!--- How are we going to solve the problem? Try to include the user journey! -->
### What does success look like, and how can we measure that?
-<!--- If no way to measure success, link to an issue that will implement a way to measure this -->
+<!--- Define both the success metrics and acceptance criteria. Note thet success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this -->
### Links / references
-/label ~"feature proposal"
+/label ~feature
diff --git a/.gitlab/issue_templates/Security Release.md b/.gitlab/issue_templates/Security Release.md
new file mode 100644
index 00000000000..1734e915ad2
--- /dev/null
+++ b/.gitlab/issue_templates/Security Release.md
@@ -0,0 +1,69 @@
+<!--
+# Read me first!
+
+Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X`
+-->
+
+## Releases tasks
+
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/release-manager.md
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/security-engineer.md
+
+## Version issues:
+
+* 11.4.X: {release task link}
+* 11.3.X: {release task link}
+* 11.2.X: {release task link}
+
+## Security Issues:
+
+### CE
+
+* {https://gitlab.com/gitlab-org/gitlab-ce/issues link}
+
+### EE
+
+* {https://gitlab.com/gitlab-org/gitlab-ee/issues link}
+
+## Security Issues in dev.gitlab.org:
+
+### CE
+
+- {https://dev.gitlab.org/gitlab/gitlabhq/issues link}
+
+| Version | MR | Status|
+|---------|----|-------|
+| 11.4 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| 11.3 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| 11.2 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| master | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+
+
+
+### EE
+
+* {https://dev.gitlab.org/gitlab/gitlabhq/issues/ link}
+
+
+| Version | MR | Status|
+|---------|----|-------|
+| 11.4| {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| 11.3 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| 11.2 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| master | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+
+
+## QA
+{QA issue link}
+
+## Blog post
+
+Dev: {https://dev.gitlab.org/gitlab/www-gitlab-com/merge_requests/ link}<br/>
+gitlab.com: {https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/ link}
+
+## Email notification
+{https://gitlab.com/gitlab-com/marketing/general/issues/ link}
+
+/label ~security
+/confidential
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 847a0f74aa2..f2ba9fdb174 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -686,17 +686,6 @@ Style/TrailingUnderscoreVariable:
- 'spec/lib/gitlab/etag_caching/middleware_spec.rb'
- 'spec/services/quick_actions/interpret_service_spec.rb'
-# Offense count: 5
-# Cop supports --auto-correct.
-# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
-# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
-Style/TrivialAccessors:
- Exclude:
- - 'app/models/external_issue.rb'
- - 'app/serializers/base_serializer.rb'
- - 'lib/gitlab/auth/ldap/person.rb'
- - 'lib/system_check/base_check.rb'
-
# Offense count: 4
# Cop supports --auto-correct.
Style/UnlessElse:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e86c818298b..b47dc4e19ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,24 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.6.5 (2019-01-17)
+
+### Fixed (5 changes)
+
+- Add syntax highlighting to suggestion diff. !24156
+- Fix broken templated "Too many changes to show" text. !24282
+- Fix requests profiler in admin page not rendering HTML properly. !24291
+- Fix no avatar not showing in user selection box. !24346
+- Fixed diff suggestions removing dashes.
+
+
+## 11.6.4 (2019-01-15)
+
+### Security (1 change)
+
+- Validate bundle files before unpacking them.
+
+
## 11.6.3 (2019-01-04)
### Fixed (1 change)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 0eed1a29efd..f88cf52e6ef 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.12.0
+1.13.0 \ No newline at end of file
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 3a3cd8cc8b0..88c5fb891dc 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.3.1
+1.4.0
diff --git a/Gemfile b/Gemfile
index 5972c434d7e..b3eeb3ec0ec 100644
--- a/Gemfile
+++ b/Gemfile
@@ -125,9 +125,9 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.8'
gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 3.1'
-gem 'truncato', '~> 0.7.9'
+gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 2.7.0'
-gem 'nokogiri', '~> 1.8.5'
+gem 'nokogiri', '~> 1.10.1'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
@@ -160,12 +160,12 @@ gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs
gem 'sidekiq', '~> 5.2.1'
-gem 'sidekiq-cron', '~> 0.6.0'
+gem 'sidekiq-cron', '~> 1.0'
gem 'redis-namespace', '~> 1.6.0'
gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
# Cron Parser
-gem 'rufus-scheduler', '~> 3.4'
+gem 'fugit', '~> 1.1'
# HTTP requests
gem 'httparty', '~> 0.13.3'
@@ -304,6 +304,12 @@ group :metrics do
gem 'raindrops', '~> 0.18'
end
+group :tracing do
+ # OpenTracing
+ gem 'opentracing', '~> 0.4.3'
+ gem 'jaeger-client', '~> 0.10.0'
+end
+
group :development do
gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 5098c6fb88e..419a6831924 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -185,7 +185,7 @@ GEM
erubi (1.7.1)
erubis (2.7.0)
escape_utils (1.2.1)
- et-orbi (1.0.3)
+ et-orbi (1.1.7)
tzinfo
eventmachine (1.2.7)
excon (0.62.0)
@@ -206,7 +206,7 @@ GEM
fast_blank (1.0.0)
fast_gettext (1.6.0)
ffaker (2.10.0)
- ffi (1.9.25)
+ ffi (1.10.0)
flipper (0.13.0)
flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
@@ -258,6 +258,9 @@ GEM
foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5)
+ fugit (1.1.7)
+ et-orbi (~> 1.1, >= 1.1.7)
+ raabro (~> 1.1)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
@@ -389,6 +392,9 @@ GEM
cause
json
ipaddress (0.8.3)
+ jaeger-client (0.10.0)
+ opentracing (~> 0.3)
+ thrift
jira-ruby (1.4.1)
activesupport
multipart-post
@@ -462,9 +468,9 @@ GEM
mimemagic (0.3.2)
mini_magick (4.8.0)
mini_mime (1.0.1)
- mini_portile2 (2.3.0)
+ mini_portile2 (2.4.0)
minitest (5.11.3)
- msgpack (1.2.4)
+ msgpack (1.2.6)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
@@ -477,8 +483,8 @@ GEM
net-ssh (5.0.1)
netrc (0.11.0)
nio4r (2.3.1)
- nokogiri (1.8.5)
- mini_portile2 (~> 2.3.0)
+ nokogiri (1.10.1)
+ mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
nokogiri
numerizer (0.1.1)
@@ -544,6 +550,8 @@ GEM
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0)
+ opentracing (0.4.3)
+ optimist (3.0.0)
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
@@ -606,6 +614,7 @@ GEM
get_process_mem (~> 0.2)
puma (>= 2.7, < 4)
pyu-ruby-sasl (0.0.3.3)
+ raabro (1.1.6)
rack (2.0.6)
rack-accept (0.4.5)
rack (>= 0.4)
@@ -664,10 +673,10 @@ GEM
ffi (>= 0.5.0, < 2)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
- rbtrace (0.4.10)
+ rbtrace (0.4.11)
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
- trollop (>= 1.16.2)
+ optimist (>= 3.0.0)
rdoc (6.0.4)
re2 (1.1.1)
recaptcha (3.0.0)
@@ -775,8 +784,6 @@ GEM
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.2)
- rufus-scheduler (3.4.0)
- et-orbi (~> 1.0)
rugged (0.27.5)
safe_yaml (1.0.4)
sanitize (4.6.6)
@@ -820,8 +827,8 @@ GEM
connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
- sidekiq-cron (0.6.0)
- rufus-scheduler (>= 3.3.0)
+ sidekiq-cron (1.0.4)
+ fugit (~> 1.1)
sidekiq (>= 4.2.1)
signet (0.11.0)
addressable (~> 2.3)
@@ -868,6 +875,7 @@ GEM
rack (>= 1, < 3)
thor (0.19.4)
thread_safe (0.3.6)
+ thrift (0.11.0.0)
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
@@ -875,10 +883,9 @@ GEM
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
- trollop (2.1.3)
- truncato (0.7.10)
+ truncato (0.7.11)
htmlentities (~> 4.3.1)
- nokogiri (~> 1.8.0, >= 1.7.0)
+ nokogiri (>= 1.7.0, <= 2.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
u2f (0.2.1)
@@ -1003,6 +1010,7 @@ DEPENDENCIES
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.84.0)
+ fugit (~> 1.1)
fuubar (~> 2.2.0)
gemojione (~> 3.3)
gettext (~> 3.2.2)
@@ -1037,6 +1045,7 @@ DEPENDENCIES
httparty (~> 0.13.3)
icalendar
influxdb (~> 0.2)
+ jaeger-client (~> 0.10.0)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
@@ -1059,7 +1068,7 @@ DEPENDENCIES
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ssh (~> 5.0)
- nokogiri (~> 1.8.5)
+ nokogiri (~> 1.10.1)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
@@ -1077,6 +1086,7 @@ DEPENDENCIES
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
+ opentracing (~> 0.4.3)
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
@@ -1127,7 +1137,6 @@ DEPENDENCIES
ruby-prof (~> 0.17.0)
ruby-progressbar
ruby_parser (~> 3.8)
- rufus-scheduler (~> 3.4)
rugged (~> 0.27)
sanitize (~> 4.6)
sass (~> 3.5)
@@ -1141,7 +1150,7 @@ DEPENDENCIES
sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2)
sidekiq (~> 5.2.1)
- sidekiq-cron (~> 0.6.0)
+ sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
simplecov (~> 0.14.0)
slack-notifier (~> 1.5.1)
@@ -1156,7 +1165,7 @@ DEPENDENCIES
thin (~> 1.7.0)
timecop (~> 0.8.0)
toml-rb (~> 1.0.0)
- truncato (~> 0.7.9)
+ truncato (~> 0.7.11)
u2f (~> 0.2.1)
uglifier (~> 2.7.2)
unf (~> 0.1.4)
diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue
index 9051be1e102..cad5611c8c5 100644
--- a/app/assets/javascripts/badges/components/badge_list_row.vue
+++ b/app/assets/javascripts/badges/components/badge_list_row.vue
@@ -55,7 +55,7 @@ export default {
:disabled="badge.isDeleting"
class="btn btn-default append-right-8"
type="button"
- @click="editBadge(badge);"
+ @click="editBadge(badge)"
>
<icon :size="16" :aria-label="__('Edit')" name="pencil" />
</button>
@@ -65,7 +65,7 @@ export default {
type="button"
data-toggle="modal"
data-target="#delete-badge-modal"
- @click="updateBadgeInModal(badge);"
+ @click="updateBadgeInModal(badge)"
>
<icon :size="16" :aria-label="__('Delete')" name="remove" />
</button>
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 30fbdb9e97f..f569322ab70 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -86,7 +86,7 @@ export default {
class="board-card"
@mousedown="mouseDown"
@mousemove="mouseMove"
- @mouseup="showIssue($event);"
+ @mouseup="showIssue($event)"
>
<issue-card-inner
:list="list"
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index f3f341ece5c..a689dfc3768 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -221,7 +221,7 @@ export default {
</script>
<template>
- <div class="board-list-component">
+ <div class="board-list-component d-flex flex-column">
<div v-if="loading" class="board-list-loading text-center" aria-label="Loading issues">
<gl-loading-icon />
</div>
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 93bcb4e129e..28d96dab605 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -96,7 +96,7 @@ export default {
<template>
<div class="board-new-issue-form">
<div class="board-card">
- <form @submit="submit($event);">
+ <form @submit="submit($event)">
<div v-if="error" class="flash-container">
<div class="flash-alert">An error occurred. Please try again.</div>
</div>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index 0f581c3d37d..90ab3a76342 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -184,7 +184,7 @@ export default {
:title="label.description"
class="badge color-label append-right-4 prepend-top-4"
type="button"
- @click="filterByLabel(label);"
+ @click="filterByLabel(label)"
>
{{ label.title }}
</button>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index defd857b92c..2a0008467c4 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -58,7 +58,7 @@ export default {
v-if="activeTab === 'selected'"
class="btn btn-default"
type="button"
- @click="changeTab('all');"
+ @click="changeTab('all')"
>
Open issues
</button>
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index b1bc7d87086..d4afd9d59da 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -71,7 +71,7 @@ export default {
<span class="inline add-issues-footer-to-list"> to list </span>
<lists-dropdown />
</div>
- <button class="btn btn-default float-right" type="button" @click="toggleModal(false);">
+ <button class="btn btn-default float-right" type="button" @click="toggleModal(false)">
Cancel
</button>
</footer>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index d0e285a149e..1f0961e02d8 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -58,7 +58,7 @@ export default {
class="close"
data-dismiss="modal"
aria-label="Close"
- @click="toggleModal(false);"
+ @click="toggleModal(false)"
>
<span aria-hidden="true">×</span>
</button>
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index 878bb002c6c..e9ed2de859d 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -130,7 +130,7 @@ export default {
<div
:class="{ 'is-active': issue.selected }"
class="board-card"
- @click="toggleIssue($event, issue);"
+ @click="toggleIssue($event, issue)"
>
<issue-card-inner :issue="issue" :issue-link-base="issueLinkBase" :root-path="rootPath" />
<icon
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
index 820d0679df5..3fbe8fe1be7 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -38,7 +38,7 @@ export default {
:class="{ 'is-active': list.id == selected.id }"
href="#"
role="button"
- @click.prevent="modal.selectedList = list;"
+ @click.prevent="modal.selectedList = list"
>
<span :style="{ backgroundColor: list.label.color }" class="dropdown-label-box"> </span>
{{ list.title }}
diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue
index 7b800a6ab97..2d2920e312e 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.vue
+++ b/app/assets/javascripts/boards/components/modal/tabs.vue
@@ -21,12 +21,12 @@ export default {
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ active: activeTab == 'all' }">
- <a href="#" role="button" @click.prevent="changeTab('all');">
+ <a href="#" role="button" @click.prevent="changeTab('all')">
Open issues <span class="badge badge-pill"> {{ issuesCount }} </span>
</a>
</li>
<li :class="{ active: activeTab == 'selected' }">
- <a href="#" role="button" @click.prevent="changeTab('selected');">
+ <a href="#" role="button" @click.prevent="changeTab('selected')">
Selected issues <span class="badge badge-pill"> {{ selectedCount }} </span>
</a>
</li>
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index d899b7fbd8c..8274647744f 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -82,7 +82,7 @@ export default {
<template>
<div>
<label class="label-bold prepend-top-10"> Project </label>
- <div ref="projectsDropdown" class="dropdown">
+ <div ref="projectsDropdown" class="dropdown dropdown-projects">
<button
class="dropdown-menu-toggle wide"
type="button"
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 42c3542b50b..50efecb3475 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -70,7 +70,7 @@ export default class ContextualSidebar {
ContextualSidebar.setCollapsedCookie(collapsed);
}
- requestIdleCallback(this.toggleSidebarOverflow);
+ requestIdleCallback(() => this.toggleSidebarOverflow());
}
toggleSidebarOverflow() {
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index f75345d31f8..f0a827be7e8 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -54,6 +54,9 @@ export default {
showDropdowns() {
return !this.commit && this.mergeRequestDiffs.length;
},
+ baseVersionPath() {
+ return this.mergeRequestDiff.base_version_path;
+ },
},
methods: {
...mapActions('diffs', [
@@ -95,6 +98,7 @@ export default {
and
<compare-versions-dropdown
:other-versions="comparableDiffs"
+ :base-version-path="baseVersionPath"
:start-version="startVersion"
:target-branch="targetBranch"
class="mr-version-compare-dropdown"
diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
index b9b1ee02697..561188c1e8f 100644
--- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
@@ -34,14 +34,13 @@ export default {
required: false,
default: false,
},
+ baseVersionPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
- baseVersion() {
- return {
- name: 'hii',
- versionIndex: -1,
- };
- },
targetVersions() {
if (this.mergeRequestVersion) {
return this.otherVersions;
@@ -62,6 +61,9 @@ export default {
);
},
href(version) {
+ if (this.isBase(version)) {
+ return this.baseVersionPath;
+ }
if (this.showCommitCount) {
return version.version_path;
}
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index ba6dcd63880..6dc2f5d3f68 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -127,7 +127,7 @@ export default {
:save-button-title="__('Comment')"
class="diff-comment-form new-note discussion-form discussion-form-container"
@handleFormUpdate="handleSaveNote"
- @cancelForm="closeDiffFileCommentForm(diffFile.file_hash);"
+ @cancelForm="closeDiffFileCommentForm(diffFile.file_hash)"
/>
</div>
</diff-viewer>
diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue
index b2021cd6061..4c73eea4049 100644
--- a/app/assets/javascripts/diffs/components/diff_discussions.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussions.vue
@@ -68,7 +68,7 @@ export default {
}"
type="button"
class="js-diff-notes-toggle"
- @click="toggleDiscussion({ discussionId: discussion.id });"
+ @click="toggleDiscussion({ discussionId: discussion.id })"
>
<icon v-if="discussion.expanded" name="collapse" class="collapse-icon" />
<template v-else>
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index f75a01b023b..b58f704bebb 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -145,7 +145,7 @@ export default {
<div
ref="header"
class="js-file-title file-title file-title-flex-parent"
- @click="handleToggleFile($event, true);"
+ @click="handleToggleFile($event, true)"
>
<div class="file-header-content">
<icon
diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
index c0613d80d37..6709df48637 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -179,7 +179,7 @@ export default {
v-if="lineNumber"
:data-linenumber="lineNumber"
:href="lineHref"
- @click="setHighlightedRow(lineCode);"
+ @click="setHighlightedRow(lineCode)"
>
</a>
<diff-gutter-avatars v-if="shouldShowAvatarsOnGutter" :discussions="line.discussions" />
diff --git a/app/assets/javascripts/diffs/components/image_diff_overlay.vue b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
index d30e64312aa..4a83c5a72a5 100644
--- a/app/assets/javascripts/diffs/components/image_diff_overlay.vue
+++ b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
@@ -97,7 +97,7 @@ export default {
v-if="canComment"
type="button"
class="btn-transparent position-absolute image-diff-overlay-add-comment w-100 h-100 js-add-image-diff-note-button"
- @click="clickedImage($event.offsetX, $event.offsetY);"
+ @click="clickedImage($event.offsetX, $event.offsetY)"
>
<span class="sr-only"> {{ __('Add image comment') }} </span>
</button>
@@ -109,7 +109,7 @@ export default {
:disabled="!shouldToggleDiscussion"
class="js-image-badge"
type="button"
- @click="toggleDiscussion({ discussionId: discussion.id });"
+ @click="toggleDiscussion({ discussionId: discussion.id })"
>
<icon v-if="showCommentIcon" name="image-comment-dark" />
<template v-else>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index eb8f274aff3..097587c5ac1 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -81,7 +81,7 @@ export default {
:placeholder="s__('MergeRequest|Filter files')"
type="search"
class="form-control"
- @focus="toggleFocusSearch(true);"
+ @focus="toggleFocusSearch(true)"
@blur="blurSearch"
/>
<button
@@ -104,7 +104,7 @@ export default {
}"
class="btn btn-default pt-0 pb-0 d-flex align-items-center"
type="button"
- @click="toggleRenderTreeList(false);"
+ @click="toggleRenderTreeList(false)"
>
<icon name="hamburger" />
</button>
@@ -117,7 +117,7 @@ export default {
}"
class="btn btn-default pt-0 pb-0 d-flex align-items-center"
type="button"
- @click="toggleRenderTreeList(true);"
+ @click="toggleRenderTreeList(true)"
>
<icon name="file-tree" />
</button>
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 78a39baa4cb..0af1ba13d36 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -32,3 +32,5 @@ export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
export const MAX_LINES_TO_BE_RENDERED = 2000;
export const MR_TREE_SHOW_KEY = 'mr_tree_show';
+
+export const TREE_TYPE = 'tree';
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 00a4bb6d3a3..196c9dfb1c2 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -5,6 +5,7 @@ import createFlash from '~/flash';
import { s__ } from '~/locale';
import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/utils/common_utils';
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
+import TreeWorker from '../workers/tree_worker';
import eventHub from '../../notes/event_hub';
import { getDiffPositionByLineCode, getNoteFormData } from './utils';
import * as types from './mutation_types';
@@ -21,17 +22,29 @@ export const setBaseConfig = ({ commit }, options) => {
};
export const fetchDiffFiles = ({ state, commit }) => {
+ const worker = new TreeWorker();
+
commit(types.SET_LOADING, true);
+ worker.addEventListener('message', ({ data }) => {
+ commit(types.SET_TREE_DATA, data);
+
+ worker.terminate();
+ });
+
return axios
.get(state.endpoint)
.then(res => {
commit(types.SET_LOADING, false);
commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []);
commit(types.SET_DIFF_DATA, res.data);
+
+ worker.postMessage(state.diffFiles);
+
return Vue.nextTick();
})
- .then(handleLocationHash);
+ .then(handleLocationHash)
+ .catch(() => worker.terminate());
};
export const setHighlightedRow = ({ commit }, lineCode) => {
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 0338cde3658..6ed8c5709a8 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -18,3 +18,5 @@ export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM';
export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM';
export const CLOSE_DIFF_FILE_COMMENT_FORM = 'CLOSE_DIFF_FILE_COMMENT_FORM';
export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW';
+
+export const SET_TREE_DATA = 'SET_TREE_DATA';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index ed4203cf5e0..00095997ba3 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -1,5 +1,4 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { sortTree } from '~/ide/stores/utils';
import {
findDiffFile,
addLineReferences,
@@ -7,7 +6,6 @@ import {
addContextLines,
prepareDiffData,
isDiscussionApplicableToLine,
- generateTreeList,
} from './utils';
import * as types from './mutation_types';
@@ -23,12 +21,9 @@ export default {
[types.SET_DIFF_DATA](state, data) {
prepareDiffData(data);
- const { tree, treeEntries } = generateTreeList(data.diff_files);
Object.assign(state, {
...convertObjectPropsToCamelCase(data),
- tree: sortTree(tree),
- treeEntries,
});
},
@@ -239,4 +234,8 @@ export default {
[types.SET_HIGHLIGHTED_ROW](state, lineCode) {
state.highlightedRow = lineCode;
},
+ [types.SET_TREE_DATA](state, { treeEntries, tree }) {
+ state.treeEntries = treeEntries;
+ state.tree = tree;
+ },
};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index f427367c11e..ada93b570b0 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -11,6 +11,7 @@ import {
MATCH_LINE_TYPE,
LINES_TO_BE_RENDERED_DIRECTLY,
MAX_LINES_TO_BE_RENDERED,
+ TREE_TYPE,
} from '../constants';
export function findDiffFile(files, hash) {
@@ -289,8 +290,63 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD
return latestDiff && discussion.active && line_code === discussion.line_code;
}
-export const generateTreeList = files =>
- files.reduce(
+export const getLowestSingleFolder = folder => {
+ const getFolder = (blob, start = []) =>
+ blob.tree.reduce(
+ (acc, file) => {
+ const shouldGetFolder = file.tree.length === 1 && file.tree[0].type === TREE_TYPE;
+ const currentFileTypeTree = file.type === TREE_TYPE;
+ const path = shouldGetFolder || currentFileTypeTree ? acc.path.concat(file.name) : acc.path;
+ const tree = shouldGetFolder || currentFileTypeTree ? acc.tree.concat(file) : acc.tree;
+
+ if (shouldGetFolder) {
+ const firstFolder = getFolder(file);
+
+ path.push(firstFolder.path);
+ tree.push(...firstFolder.tree);
+ }
+
+ return {
+ ...acc,
+ path,
+ tree,
+ };
+ },
+ { path: start, tree: [] },
+ );
+ const { path, tree } = getFolder(folder, [folder.name]);
+
+ return {
+ path: path.join('/'),
+ treeAcc: tree.length ? tree[tree.length - 1].tree : null,
+ };
+};
+
+export const flattenTree = tree => {
+ const flatten = blobTree =>
+ blobTree.reduce((acc, file) => {
+ const blob = file;
+ let treeToFlatten = blob.tree;
+
+ if (file.type === TREE_TYPE && file.tree.length === 1) {
+ const { treeAcc, path } = getLowestSingleFolder(file);
+
+ if (treeAcc) {
+ blob.name = path;
+ treeToFlatten = flatten(treeAcc);
+ }
+ }
+
+ blob.tree = flatten(treeToFlatten);
+
+ return acc.concat(blob);
+ }, []);
+
+ return flatten(tree);
+};
+
+export const generateTreeList = files => {
+ const { treeEntries, tree } = files.reduce(
(acc, file) => {
const split = file.new_path.split('/');
@@ -335,6 +391,9 @@ export const generateTreeList = files =>
{ treeEntries: {}, tree: [] },
);
+ return { treeEntries, tree: flattenTree(tree) };
+};
+
export const getDiffMode = diffFile => {
const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]);
return (
diff --git a/app/assets/javascripts/diffs/workers/tree_worker.js b/app/assets/javascripts/diffs/workers/tree_worker.js
new file mode 100644
index 00000000000..534d737c77e
--- /dev/null
+++ b/app/assets/javascripts/diffs/workers/tree_worker.js
@@ -0,0 +1,14 @@
+import { sortTree } from '~/ide/stores/utils';
+import { generateTreeList } from '../store/utils';
+
+// eslint-disable-next-line no-restricted-globals
+self.addEventListener('message', e => {
+ const { data } = e;
+ const { treeEntries, tree } = generateTreeList(data);
+
+ // eslint-disable-next-line no-restricted-globals
+ self.postMessage({
+ treeEntries,
+ tree: sortTree(tree),
+ });
+});
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index 1f7dab9fbd2..208bd19f6b0 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -92,7 +92,7 @@ export default {
:disabled="isActionDisabled(action)"
type="button"
class="js-manual-action-link no-btn btn d-flex align-items-center"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
>
<span class="flex-fill"> {{ action.name }} </span>
<span v-if="action.scheduledAt" class="text-secondary">
diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
index 6b1a934d3fe..19bc3313373 100644
--- a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
+++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
@@ -66,7 +66,7 @@ export default {
<button
type="button"
class="filtered-search-history-dropdown-item"
- @click="onItemActivated(item.text);"
+ @click="onItemActivated(item.text)"
>
<span>
<span
@@ -88,7 +88,7 @@ export default {
<button
type="button"
class="filtered-search-history-clear-button"
- @click="onRequestClearRecentSearches($event);"
+ @click="onRequestClearRecentSearches($event)"
>
Clear recent searches
</button>
diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue
index 63531f1f246..968e255e1fc 100644
--- a/app/assets/javascripts/frequent_items/components/app.vue
+++ b/app/assets/javascripts/frequent_items/components/app.vue
@@ -47,6 +47,12 @@ export default {
}
eventHub.$on(`${this.namespace}-dropdownOpen`, this.dropdownOpenHandler);
+
+ // As we init it through requestIdleCallback it could be that the dropdown is already open
+ const namespaceDropdown = document.getElementById(`nav-${this.namespace}-dropdown`);
+ if (namespaceDropdown && namespaceDropdown.classList.contains('show')) {
+ this.dropdownOpenHandler();
+ }
},
beforeDestroy() {
eventHub.$off(`${this.namespace}-dropdownOpen`, this.dropdownOpenHandler);
diff --git a/app/assets/javascripts/frequent_items/index.js b/app/assets/javascripts/frequent_items/index.js
index 5157ff211dc..6263acbab8e 100644
--- a/app/assets/javascripts/frequent_items/index.js
+++ b/app/assets/javascripts/frequent_items/index.js
@@ -17,7 +17,7 @@ const frequentItemDropdowns = [
},
];
-document.addEventListener('DOMContentLoaded', () => {
+const initFrequentItemDropdowns = () => {
frequentItemDropdowns.forEach(dropdown => {
const { namespace, key } = dropdown;
const el = document.getElementById(`js-${namespace}-dropdown`);
@@ -66,4 +66,8 @@ document.addEventListener('DOMContentLoaded', () => {
},
});
});
+};
+
+document.addEventListener('DOMContentLoaded', () => {
+ requestIdleCallback(initFrequentItemDropdowns);
});
diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue
index a1f66ff764d..7c769ab7fa0 100644
--- a/app/assets/javascripts/ide/components/activity_bar.vue
+++ b/app/assets/javascripts/ide/components/activity_bar.vue
@@ -45,7 +45,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-edit-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.edit);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.edit)"
>
<icon name="code" />
</button>
@@ -62,7 +62,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-review-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.review);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.review)"
>
<icon name="file-modified" />
</button>
@@ -79,7 +79,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-commit-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.commit);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.commit)"
>
<icon name="commit" />
</button>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
index 6f1ded91753..00b2d236da3 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
@@ -111,8 +111,8 @@ export default {
name="commit-message"
@scroll="handleScroll"
@input="onInput"
- @focus="updateIsFocused(true);"
- @blur="updateIsFocused(false);"
+ @focus="updateIsFocused(true)"
+ @blur="updateIsFocused(false)"
@keydown.ctrl.enter="onCtrlEnter"
@keydown.meta.enter="onCtrlEnter"
>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
index 3525084b1cb..2b44438f849 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -65,7 +65,7 @@ export default {
:disabled="disabled"
type="radio"
name="commit-action"
- @change="updateCommitAction($event.target.value);"
+ @change="updateCommitAction($event.target.value)"
/>
<span class="prepend-left-10">
<span v-if="label" class="ide-radio-label"> {{ label }} </span> <slot v-else></slot>
@@ -76,7 +76,7 @@ export default {
:placeholder="newBranchName"
type="text"
class="form-control monospace"
- @input="updateBranchName($event.target.value);"
+ @input="updateBranchName($event.target.value)"
/>
</div>
</fieldset>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
index 02c2004d495..e054be86c1e 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -48,7 +48,7 @@ export default {
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click.stop.prevent="stageChange(path);"
+ @click.stop.prevent="stageChange(path)"
>
<icon :size="16" name="mobile-issue-close" class="ml-auto mr-auto" />
</button>
@@ -70,7 +70,7 @@ export default {
:header-title-text="modalTitle"
:footer-primary-button-text="__('Discard changes')"
footer-primary-button-variant="danger"
- @submit="discardFileChanges(path);"
+ @submit="discardFileChanges(path)"
>
{{ __("You will loose all changes you've made to this file. This action cannot be undone.") }}
</gl-modal>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
index ce41fcdb087..0567ef54ff3 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
@@ -33,7 +33,7 @@ export default {
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click.stop.prevent="unstageChange(path);"
+ @click.stop.prevent="unstageChange(path)"
>
<icon :size="16" name="redo" class="ml-auto mr-auto" />
</button>
diff --git a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
index 5f99261ec39..732fa0786b0 100644
--- a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
+++ b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
@@ -40,7 +40,7 @@ export default {
'is-active': viewer === $options.viewerTypes.mr,
}"
href="#"
- @click.prevent="changeMode($options.viewerTypes.mr);"
+ @click.prevent="changeMode($options.viewerTypes.mr)"
>
<strong class="dropdown-menu-inner-title"> {{ mergeReviewLine }} </strong>
<span class="dropdown-menu-inner-content">
@@ -54,7 +54,7 @@ export default {
'is-active': viewer === $options.viewerTypes.diff,
}"
href="#"
- @click.prevent="changeMode($options.viewerTypes.diff);"
+ @click.prevent="changeMode($options.viewerTypes.diff)"
>
<strong class="dropdown-menu-inner-title">{{ __('Reviewing') }}</strong>
<span class="dropdown-menu-inner-content">
diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue
index bb391912572..0b0cd7b75eb 100644
--- a/app/assets/javascripts/ide/components/file_finder/index.vue
+++ b/app/assets/javascripts/ide/components/file_finder/index.vue
@@ -164,7 +164,7 @@ export default {
</script>
<template>
- <div class="ide-file-finder-overlay" @mousedown.self="toggleFileFinder(false);">
+ <div class="ide-file-finder-overlay" @mousedown.self="toggleFileFinder(false)">
<div class="dropdown-menu diff-file-changes ide-file-finder show">
<div class="dropdown-input">
<input
@@ -174,8 +174,8 @@ export default {
type="search"
class="dropdown-input-field"
autocomplete="off"
- @keydown="onKeydown($event);"
- @keyup="onKeyup($event);"
+ @keydown="onKeydown($event)"
+ @keyup="onKeyup($event)"
/>
<i
:class="{
diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
index 414ea9c7d4d..343e0cca672 100644
--- a/app/assets/javascripts/ide/components/file_templates/dropdown.vue
+++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
@@ -91,7 +91,7 @@ export default {
<gl-loading-icon v-if="showLoading" :size="2" />
<ul v-else>
<li v-for="(item, index) in outputData" :key="index">
- <button type="button" @click="clickItem(item);">{{ item.name }}</button>
+ <button type="button" @click="clickItem(item)">{{ item.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index e2e0acc22b1..f1d40586903 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -84,7 +84,7 @@ export default {
<button
type="button"
class="p-0 border-0 h-50"
- @click="openRightPane($options.rightSidebarViews.pipelines);"
+ @click="openRightPane($options.rightSidebarViews.pipelines)"
>
<ci-icon
v-tooltip
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 9fc21adae7c..f93496132a4 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -43,7 +43,7 @@ export default {
:show-label="false"
class="d-flex border-0 p-0 mr-3 qa-new-file"
icon="doc-new"
- @click="openNewEntryModal({ type: 'blob' });"
+ @click="openNewEntryModal({ type: 'blob' })"
/>
<upload
:show-label="false"
@@ -56,7 +56,7 @@ export default {
:show-label="false"
class="d-flex border-0 p-0"
icon="folder-new"
- @click="openNewEntryModal({ type: 'tree' });"
+ @click="openNewEntryModal({ type: 'tree' })"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue
index e8fe5fc696d..7710bfb49ec 100644
--- a/app/assets/javascripts/ide/components/jobs/detail.vue
+++ b/app/assets/javascripts/ide/components/jobs/detail.vue
@@ -75,7 +75,7 @@ export default {
<template>
<div class="ide-pipeline build-page d-flex flex-column flex-fill">
<header class="ide-job-header d-flex align-items-center">
- <button class="btn btn-default btn-sm d-flex" @click="setDetailJob(null);">
+ <button class="btn btn-default btn-sm d-flex" @click="setDetailJob(null)">
<icon name="chevron-left" /> {{ __('View jobs') }}
</button>
</header>
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index ac2b0eddfb4..2d55ffb3c65 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -84,7 +84,7 @@ export default {
:placeholder="__('Search merge requests')"
@focus="onSearchFocus"
@input="searchMergeRequests"
- @removeToken="setSearchType(null);"
+ @removeToken="setSearchType(null)"
/>
<icon :size="18" name="search" class="input-icon" />
</div>
@@ -102,7 +102,7 @@ export default {
<button
type="button"
class="btn-link d-flex align-items-center"
- @click.stop="setSearchType(searchType);"
+ @click.stop="setSearchType(searchType)"
>
<span class="d-flex append-right-default ide-search-list-current-icon">
<icon :size="18" name="search" />
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index a50d729036f..d7a7b1b4d78 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -73,7 +73,7 @@ export default {
:aria-label="__('Create new file or directory')"
type="button"
class="rounded border-0 d-flex ide-entry-dropdown-toggle"
- @click.stop="openDropdown();"
+ @click.stop="openDropdown()"
>
<icon name="ellipsis_v" /> <icon name="arrow-down" />
</button>
@@ -85,7 +85,7 @@ export default {
class="d-flex"
icon="doc-new"
icon-classes="mr-2"
- @click="createNewItem('blob');"
+ @click="createNewItem('blob')"
/>
</li>
<li><upload :path="path" @create="createTempEntry" /></li>
@@ -95,7 +95,7 @@ export default {
class="d-flex"
icon="folder-new"
icon-classes="mr-2"
- @click="createNewItem($options.modalTypes.tree);"
+ @click="createNewItem($options.modalTypes.tree)"
/>
</li>
<li class="divider"></li>
@@ -106,7 +106,7 @@ export default {
class="d-flex"
icon="pencil"
icon-classes="mr-2"
- @click="createNewItem($options.modalTypes.rename);"
+ @click="createNewItem($options.modalTypes.rename)"
/>
</li>
<li>
@@ -115,7 +115,7 @@ export default {
class="d-flex"
icon="remove"
icon-classes="mr-2"
- @click="deleteEntry(path);"
+ @click="deleteEntry(path)"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index 63cbf41b89b..04ecd4ba4e7 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -114,7 +114,7 @@ export default {
<button
type="button"
class="btn btn-missing p-1 pr-2 pl-2"
- @click="createFromTemplate(template);"
+ @click="createFromTemplate(template)"
>
{{ template.name }}
</button>
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index 7a57ccf2dd3..2e6bd85feec 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -122,7 +122,7 @@ export default {
data-placement="left"
class="ide-sidebar-link is-right"
type="button"
- @click="clickTab($event, tab);"
+ @click="clickTab($event, tab)"
>
<icon :size="16" :name="tab.icon" />
</button>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index c13d3ec094b..94a9e87369c 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -219,7 +219,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
- @click.prevent="setFileViewMode({ file, viewMode: 'editor' });"
+ @click.prevent="setFileViewMode({ file, viewMode: 'editor' })"
>
<template v-if="viewer === $options.viewerTypes.edit">
{{ __('Edit') }}
@@ -233,7 +233,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
- @click.prevent="setFileViewMode({ file, viewMode: 'preview' });"
+ @click.prevent="setFileViewMode({ file, viewMode: 'preview' })"
>
{{ file.previewMode.previewTitle }}
</a>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index 4b87b83db8a..f6aa2295844 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -74,7 +74,7 @@ export default {
active: tab.active,
disabled: tab.pending,
}"
- @click="clickFile(tab);"
+ @click="clickFile(tab)"
@mouseover="mouseOverTab"
@mouseout="mouseOutTab"
>
@@ -88,7 +88,7 @@ export default {
:disabled="tab.pending"
type="button"
class="multi-file-tab-close"
- @click.stop.prevent="closeFile(tab);"
+ @click.stop.prevent="closeFile(tab)"
>
<icon v-if="!showChangedIcon" :size="12" name="close" />
<changed-file-icon v-else :file="tab" />
diff --git a/app/assets/javascripts/ide/components/resizable_panel.vue b/app/assets/javascripts/ide/components/resizable_panel.vue
index a89de56ab5c..7277fcb7617 100644
--- a/app/assets/javascripts/ide/components/resizable_panel.vue
+++ b/app/assets/javascripts/ide/components/resizable_panel.vue
@@ -78,8 +78,8 @@ export default {
:min-size="minSize"
:max-size="$options.maxSize"
:side="side === 'right' ? 'left' : 'right'"
- @resize-start="setResizingStatus(true);"
- @resize-end="setResizingStatus(false);"
+ @resize-start="setResizingStatus(true)"
+ @resize-end="setResizingStatus(false)"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/shared/tokened_input.vue b/app/assets/javascripts/ide/components/shared/tokened_input.vue
index f58e08c2cc9..de3e71dad92 100644
--- a/app/assets/javascripts/ide/components/shared/tokened_input.vue
+++ b/app/assets/javascripts/ide/components/shared/tokened_input.vue
@@ -76,8 +76,8 @@ export default {
<button
class="selectable btn-blank"
type="button"
- @click.stop="removeToken(token);"
- @keyup.delete="removeToken(token);"
+ @click.stop="removeToken(token)"
+ @keyup.delete="removeToken(token)"
>
<div class="value-container rounded">
<div class="value">{{ token.label }}</div>
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index 7f79e92067f..91332c21b52 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -55,7 +55,7 @@ export default {
<ul class="dropdown-menu">
<li v-for="stage in stages" :key="stage.name">
- <button type="button" class="js-stage-item stage-item" @click="onStageClick(stage);">
+ <button type="button" class="js-stage-item stage-item" @click="onStageClick(stage)">
{{ stage.name }}
</button>
</li>
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index 64a1df80a8e..309b73f5a4d 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -257,8 +257,8 @@ export default {
<template>
<div
class="prometheus-graph"
- @mouseover="showFlagContent = true;"
- @mouseleave="showFlagContent = false;"
+ @mouseover="showFlagContent = true"
+ @mouseleave="showFlagContent = false"
>
<div class="prometheus-graph-header">
<h5 class="prometheus-graph-title">{{ graphData.title }}</h5>
@@ -300,7 +300,7 @@ export default {
:height="graphHeight - 100"
class="prometheus-graph-overlay"
transform="translate(-5, 20)"
- @mousemove="handleMouseOverGraph($event);"
+ @mousemove="handleMouseOverGraph($event)"
/>
</svg>
<svg v-else :viewBox="innerViewBox" class="js-no-data-to-display">
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 7c17147dd01..d669ba5a8fa 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -357,9 +357,9 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
- @keydown.up="editCurrentUserLastNote();"
- @keydown.meta.enter="handleSave();"
- @keydown.ctrl.enter="handleSave();"
+ @keydown.up="editCurrentUserLastNote()"
+ @keydown.meta.enter="handleSave()"
+ @keydown.ctrl.enter="handleSave()"
>
</textarea>
</markdown-field>
@@ -373,7 +373,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-success js-comment-button js-comment-submit-button
qa-comment-button"
type="submit"
- @click.prevent="handleSave();"
+ @click.prevent="handleSave()"
>
{{ __(commentButtonTitle) }}
</button>
@@ -394,7 +394,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<button
type="button"
class="btn btn-transparent"
- @click.prevent="setNoteType('comment');"
+ @click.prevent="setNoteType('comment')"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
@@ -408,7 +408,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<button
type="button"
class="btn btn-transparent qa-discussion-option"
- @click.prevent="setNoteType('discussion');"
+ @click.prevent="setNoteType('discussion')"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
@@ -429,7 +429,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
]"
:disabled="isToggleStateButtonLoading || isSubmitting"
:label="issueActionButtonTitle"
- @click="handleSave(true);"
+ @click="handleSave(true)"
/>
</div>
</form>
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 2d7c04ea614..e03d6e9cd02 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -112,7 +112,7 @@ export default {
:class="{ 'is-active': filter.value === currentValue }"
class="qa-filter-options"
type="button"
- @click="selectFilter(filter.value);"
+ @click="selectFilter(filter.value)"
>
{{ filter.title }}
</button>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index bde00ea87ff..3efdd1c5c17 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -174,7 +174,7 @@ export default {
data-placement="bottom"
class="btn award-control"
type="button"
- @click="handleAward(awardName);"
+ @click="handleAward(awardName)"
>
<span v-html="getAwardHTML(awardName)"></span>
<span class="award-control-text js-counter">{{ awardList.length }}</span>
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index db62ddb3ecd..6dbb858e93d 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -219,10 +219,10 @@ export default {
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
- @keydown.meta.enter="handleKeySubmit();"
- @keydown.ctrl.enter="handleKeySubmit();"
- @keydown.up="editMyLastNote();"
- @keydown.esc="cancelHandler(true);"
+ @keydown.meta.enter="handleKeySubmit()"
+ @keydown.ctrl.enter="handleKeySubmit()"
+ @keydown.up="editMyLastNote()"
+ @keydown.esc="cancelHandler(true)"
></textarea>
</markdown-field>
<div class="note-form-actions clearfix">
@@ -230,21 +230,21 @@ export default {
:disabled="isDisabled"
type="button"
class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
- @click="handleUpdate();"
+ @click="handleUpdate()"
>
{{ saveButtonTitle }}
</button>
<button
v-if="discussion.resolvable"
class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
- @click.prevent="handleUpdate(true);"
+ @click.prevent="handleUpdate(true)"
>
{{ resolveButtonTitle }}
</button>
<button
class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
type="button"
- @click="cancelHandler();"
+ @click="cancelHandler()"
>
Cancel
</button>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 7c3f5d00308..4480ec74182 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -440,7 +440,7 @@ Please check your network connection and try again.`;
<button
type="button"
class="btn btn-default ml-sm-2"
- @click="resolveHandler();"
+ @click="resolveHandler()"
>
<i v-if="isResolving" aria-hidden="true" class="fa fa-spinner fa-spin"></i>
{{ resolveButtonTitle }}
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index db2a4041ec0..bd4309e47ad 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -70,7 +70,7 @@ export default {
:checked="isEditable"
class="label-bold"
type="radio"
- @click="toggleCustomInput(true);"
+ @click="toggleCustomInput(true)"
/>
<label for="custom"> {{ s__('PipelineSheduleIntervalPattern|Custom') }} </label>
@@ -88,7 +88,7 @@ export default {
:value="cronIntervalPresets.everyDay"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-day"> {{ __('Every day (at 4:00am)') }} </label>
@@ -102,7 +102,7 @@ export default {
:value="cronIntervalPresets.everyWeek"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-week">
@@ -118,7 +118,7 @@ export default {
:value="cronIntervalPresets.everyMonth"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-month">
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 2e9f2519fcb..0152e2fbe04 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -77,7 +77,7 @@ export default {
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)"
class="js-pipeline-action-link no-btn btn"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
>
{{ action.name }}
<span v-if="action.scheduled_at" class="pull-right">
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index 2d3f667e73e..7426936515a 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -172,8 +172,6 @@ export default {
<span :aria-label="stage.title" aria-hidden="true" class="no-pointer-events">
<icon :name="borderlessIcon" />
</span>
-
- <i class="fa fa-caret-down" aria-hidden="true"> </i>
</button>
<div
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
index 21095fcba16..83811ab489a 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
@@ -108,9 +108,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.id">
- <button type="button" @click.prevent="setItem(result.name);">
- {{ result.name }}
- </button>
+ <button type="button" @click.prevent="setItem(result.name)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
index 056584c8865..a2eb79af4f9 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
@@ -169,7 +169,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.project_number">
- <button type="button" @click.prevent="setItem(result);">{{ result.name }}</button>
+ <button type="button" @click.prevent="setItem(result)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
index 728616a441f..5f8a4946f4a 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
@@ -82,9 +82,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.id">
- <button type="button" @click.prevent="setItem(result.name);">
- {{ result.name }}
- </button>
+ <button type="button" @click.prevent="setItem(result.name)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 2c19973a114..81fe0a48c06 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -106,7 +106,7 @@ export default {
:aria-label="s__('ContainerRegistry|Remove tag')"
variant="danger"
class="js-delete-registry d-none d-sm-block float-right"
- @click="handleDeleteRegistry(item);"
+ @click="handleDeleteRegistry(item)"
>
<icon name="remove" />
</gl-button>
diff --git a/app/assets/javascripts/reports/components/modal_open_name.vue b/app/assets/javascripts/reports/components/modal_open_name.vue
index 118e4b02c46..4f81cee2a38 100644
--- a/app/assets/javascripts/reports/components/modal_open_name.vue
+++ b/app/assets/javascripts/reports/components/modal_open_name.vue
@@ -26,7 +26,7 @@ export default {
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
- @click="handleIssueClick();"
+ @click="handleIssueClick()"
>
{{ issue.title }}
</button>
diff --git a/app/assets/javascripts/reports/components/test_issue_body.vue b/app/assets/javascripts/reports/components/test_issue_body.vue
index 938e83de546..7700f49bf7d 100644
--- a/app/assets/javascripts/reports/components/test_issue_body.vue
+++ b/app/assets/javascripts/reports/components/test_issue_body.vue
@@ -30,7 +30,7 @@ export default {
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
- @click="openModal({ issue });"
+ @click="openModal({ issue })"
>
<div v-if="isNew" class="badge badge-danger append-right-5">{{ s__('New') }}</div>
{{ issue.name }}
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index f04f7606976..7f86741ed29 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -219,7 +219,7 @@ export default {
name="button"
type="button"
class="js-clear-user-status-button clear-user-status btn"
- @click="clearStatusInputs();"
+ @click="clearStatusInputs()"
>
<icon name="close" />
</button>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index d3a4f9c81e0..c03b2a68c78 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -102,13 +102,13 @@ export default {
/>
<div class="title hide-collapsed">
{{ __('Time tracking') }}
- <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true);">
+ <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true)">
<i class="fa fa-question-circle" aria-hidden="true"> </i>
</div>
<div
v-if="showHelpState"
class="close-help-button float-right"
- @click="toggleHelpState(false);"
+ @click="toggleHelpState(false)"
>
<i class="fa fa-close" aria-hidden="true"> </i>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 5df891aebf3..0cafa73362e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -241,7 +241,7 @@ export default {
:class="mergeButtonClass"
type="button"
class="qa-merge-button"
- @click="handleMergeButtonClick();"
+ @click="handleMergeButtonClick()"
>
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
{{ mergeButtonText }}
@@ -265,7 +265,7 @@ export default {
<a
class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
href="#"
- @click.prevent="handleMergeButtonClick(true);"
+ @click.prevent="handleMergeButtonClick(true)"
>
<span class="media">
<span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
@@ -277,7 +277,7 @@ export default {
<a
class="accept-merge-request qa-merge-immediately-option"
href="#"
- @click.prevent="handleMergeButtonClick(false, true);"
+ @click.prevent="handleMergeButtonClick(false, true)"
>
<span class="media">
<span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
index bbb9491d6cf..b1f5655a15a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
@@ -37,7 +37,7 @@ export default {
type="checkbox"
name="squash"
class="qa-squash-checkbox"
- @change="$emit('input', $event.target.checked);"
+ @change="$emit('input', $event.target.checked)"
/>
{{ __('Squash commits') }}
</label>
diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue
index 4abf795f7bd..eabf5d4bf60 100644
--- a/app/assets/javascripts/vue_shared/components/bar_chart.vue
+++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue
@@ -293,8 +293,8 @@ export default {
:title="setTooltipTitle(data)"
class="bar-rect"
data-placement="top"
- @mouseover="barHoveredIn(index);"
- @mouseout="barHoveredOut(index);"
+ @mouseover="barHoveredIn(index)"
+ @mouseout="barHoveredOut(index)"
/>
</template>
</g>
diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
index 2129f90d497..36b3ee05456 100644
--- a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
@@ -95,7 +95,7 @@ export default {
class="close float-right"
data-dismiss="modal"
aria-label="Close"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>
</button>
@@ -112,7 +112,7 @@ export default {
type="button"
class="btn"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
{{ closeButtonLabel }}
</button>
@@ -130,7 +130,7 @@ export default {
type="button"
class="btn js-primary-button"
data-dismiss="modal"
- @click="emitSubmit($event);"
+ @click="emitSubmit($event)"
>
{{ primaryButtonLabel }}
</button>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
index d5fda7e4ed3..cab92297ca7 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
@@ -75,7 +75,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.twoup,
}"
- @click="changeMode($options.imageViewMode.twoup);"
+ @click="changeMode($options.imageViewMode.twoup)"
>
{{ s__('ImageDiffViewer|2-up') }}
</li>
@@ -83,7 +83,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.swipe,
}"
- @click="changeMode($options.imageViewMode.swipe);"
+ @click="changeMode($options.imageViewMode.swipe)"
>
{{ s__('ImageDiffViewer|Swipe') }}
</li>
@@ -91,7 +91,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.onion,
}"
- @click="changeMode($options.imageViewMode.onion);"
+ @click="changeMode($options.imageViewMode.onion)"
>
{{ s__('ImageDiffViewer|Onion skin') }}
</li>
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 4c884c55a30..f54033efc54 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -139,8 +139,8 @@ export default {
class="file-row"
role="button"
@click="clickFile"
- @mouseover="toggleHover(true);"
- @mouseout="toggleHover(false);"
+ @mouseover="toggleHover(true)"
+ @mouseout="toggleHover(false)"
>
<div class="file-row-name-container">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
index faf4181bbaf..438851e5ac7 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -81,7 +81,7 @@ export default {
type="button"
class="close js-modal-close-action"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>
</button>
@@ -96,7 +96,7 @@ export default {
type="button"
class="btn js-modal-cancel-action qa-modal-cancel-button"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
{{ s__('Modal|Cancel') }}
</button>
@@ -105,7 +105,7 @@ export default {
type="button"
class="btn js-modal-primary-action qa-modal-primary-button"
data-dismiss="modal"
- @click="emitSubmit($event);"
+ @click="emitSubmit($event)"
>
{{ footerPrimaryButtonText }}
</button>
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index c830f5b49b6..3f45dc7853b 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -148,7 +148,7 @@ export default {
:class="action.cssClass"
container-class="d-inline"
:label="action.label"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
/>
</template>
</section>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index bf4d42670ee..dbfa32cd0ce 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -78,12 +78,7 @@ export default {
<div class="md-header">
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }" class="md-header-tab">
- <button
- class="js-write-link"
- tabindex="-1"
- type="button"
- @click="writeMarkdownTab($event);"
- >
+ <button class="js-write-link" tabindex="-1" type="button" @click="writeMarkdownTab($event)">
Write
</button>
</li>
@@ -92,7 +87,7 @@ export default {
class="js-preview-link js-md-preview-button"
tabindex="-1"
type="button"
- @click="previewMarkdownTab($event);"
+ @click="previewMarkdownTab($event)"
>
Preview
</button>
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index 09a64502819..f8983a3d29a 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -58,7 +58,7 @@ export default {
active: tab.isActive,
}"
>
- <a :class="`js-${scope}-tab-${tab.scope}`" role="button" @click="onTabClick(tab);">
+ <a :class="`js-${scope}-tab-${tab.scope}`" role="button" @click="onTabClick(tab)">
{{ tab.name }}
<span v-if="shouldRenderBadge(tab.count)" class="badge badge-pill"> {{ tab.count }} </span>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 31df26f7b05..b0af8399955 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -97,7 +97,7 @@ export default {
v-html="note.note_html"
></div>
<div v-if="hasMoreCommits" class="flex-list">
- <div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded;">
+ <div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded">
<icon :name="toggleIcon" :size="8" class="append-right-5" />
<span>Toggle commit list</span>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index 1c6c3fc4734..df19906309c 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -19,7 +19,7 @@ export default {
data() {
return {
script: {},
- scriptSrc: 'https://www.google.com/recaptcha/api.js',
+ scriptSrc: 'https://www.recaptcha.net/recaptcha/api.js',
};
},
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
index 82067129c57..6c0c7f15943 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -134,7 +134,7 @@ export default {
<button
type="button"
class="btn-blank btn-link btn-secondary-hover-link"
- @click="newDateSelected(null);"
+ @click="newDateSelected(null)"
>
remove
</button>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 01e655d27e5..2a34b4630f2 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -149,7 +149,7 @@ export default {
}"
class="page-item"
>
- <a class="page-link" @click.prevent="changePage(item.title, item.disabled);">
+ <a class="page-link" @click.prevent="changePage(item.title, item.disabled)">
{{ item.title }}
</a>
</li>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue
index 7361867edc5..8eaf8386b99 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue
@@ -23,6 +23,11 @@ export default {
required: false,
default: 20,
},
+ emptyText: {
+ type: String,
+ required: false,
+ default: __('None'),
+ },
},
data() {
return {
@@ -65,7 +70,8 @@ export default {
</script>
<template>
- <div>
+ <div v-if="!items.length">{{ emptyText }}</div>
+ <div v-else>
<user-avatar-link
v-for="item in visibleItems"
:key="item.id"
diff --git a/app/assets/javascripts/vuex_shared/modules/modal/actions.js b/app/assets/javascripts/vuex_shared/modules/modal/actions.js
index 552237e05c5..7b209909f69 100644
--- a/app/assets/javascripts/vuex_shared/modules/modal/actions.js
+++ b/app/assets/javascripts/vuex_shared/modules/modal/actions.js
@@ -15,3 +15,6 @@ export const show = ({ commit }) => {
export const hide = ({ commit }) => {
commit(types.HIDE);
};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index cb01a41cb7e..b90db135b4a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -129,7 +129,7 @@
@extend .dropdown-toggle;
padding-right: 25px;
position: relative;
- width: 163px;
+ width: 160px;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index d5f8e3fb4ee..1dfe2a69a2f 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -34,3 +34,12 @@ $h3-font-size: 14px * 1.75;
$h4-font-size: 14px * 1.5;
$h5-font-size: 14px * 1.25;
$h6-font-size: 14px;
+$spacer: $grid-size;
+$spacers: (
+ 0: 0,
+ 1: ($spacer * .5),
+ 2: ($spacer),
+ 3: ($spacer * 2),
+ 4: ($spacer * 3),
+ 5: ($spacer * 4)
+);
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index e1d1e598da8..bc28ffb3a92 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -26,6 +26,12 @@
opacity: 0.3;
}
+.dropdown-projects {
+ .dropdown-content {
+ max-height: 200px;
+ }
+}
+
.dropdown-menu-issues-board-new {
width: 320px;
@@ -167,6 +173,7 @@
background: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;
+ flex: 1;
}
.board-header {
@@ -228,9 +235,11 @@
}
.board-blank-state {
- height: calc(100% - 49px);
padding: $gl-padding;
background-color: $white-light;
+ flex: 1;
+ overflow-y: auto;
+ overflow-x: hidden;
}
.board-blank-state-list {
@@ -252,9 +261,9 @@
}
.board-list-component {
- height: calc(100% - 49px);
- overflow: hidden;
position: relative;
+ flex: 1;
+ min-height: 0; // firefox fix
}
.board-list {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 058b0ffef5f..a28921592ec 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -256,14 +256,25 @@
}
}
- .mini-pipeline-graph-dropdown-toggle svg {
- height: $ci-action-icon-size;
- width: $ci-action-icon-size;
- position: absolute;
- top: -1px;
- left: -1px;
- z-index: 2;
- overflow: visible;
+ .mini-pipeline-graph-dropdown-toggle {
+ svg {
+ height: $ci-action-icon-size;
+ width: $ci-action-icon-size;
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ z-index: 2;
+ overflow: visible;
+ }
+
+ &:hover,
+ &:active,
+ &:focus {
+ svg {
+ top: -2px;
+ left: -2px;
+ }
+ }
}
.stage-container {
@@ -293,7 +304,7 @@
width: 7px;
position: absolute;
right: -7px;
- top: 10px;
+ top: 11px;
border-bottom: 2px solid $border-color;
}
}
@@ -708,21 +719,43 @@
font-weight: $gl-font-weight-normal;
}
-@mixin mini-pipeline-graph-color($color-light, $color-main, $color-dark) {
- border-color: $color-main;
- color: $color-main;
+@mixin mini-pipeline-graph-color(
+ $color-background-default,
+ $color-background-hover-focus,
+ $color-background-active,
+ $color-foreground-default,
+ $color-foreground-hover-focus,
+ $color-foreground-active
+) {
+ background-color: $color-background-default;
+ border-color: $color-foreground-default;
+
+ svg {
+ fill: $color-foreground-default;
+ }
&:hover,
- &:focus,
+ &:focus {
+ background-color: $color-background-hover-focus;
+ border-color: $color-foreground-hover-focus;
+
+ svg {
+ fill: $color-foreground-hover-focus;
+ }
+ }
+
&:active {
- background-color: $color-light;
- border-color: $color-dark;
- color: $color-dark;
+ background-color: $color-background-active;
+ border-color: $color-foreground-active;
svg {
- fill: $color-dark;
+ fill: $color-foreground-active;
}
}
+
+ &:focus {
+ box-shadow: 0 0 4px 1px $blue-300;
+ }
}
@mixin mini-pipeline-item() {
@@ -734,26 +767,32 @@
height: $ci-action-icon-size;
margin: 0;
padding: 0;
- transition: all 0.2s linear;
position: relative;
vertical-align: middle;
+ &:hover,
+ &:active,
+ &:focus {
+ outline: none;
+ border-width: 2px;
+ }
+
// Dropdown button animation in mini pipeline graph
&.ci-status-icon-success {
- @include mini-pipeline-graph-color($green-100, $green-500, $green-600);
+ @include mini-pipeline-graph-color($white, $green-100, $green-200, $green-500, $green-600, $green-700);
}
&.ci-status-icon-failed {
- @include mini-pipeline-graph-color($red-100, $red-500, $red-600);
+ @include mini-pipeline-graph-color($white, $red-100, $red-200, $red-500, $red-600, $red-700);
}
&.ci-status-icon-pending,
&.ci-status-icon-success_with_warnings {
- @include mini-pipeline-graph-color($orange-100, $orange-500, $orange-600);
+ @include mini-pipeline-graph-color($white, $orange-100, $orange-200, $orange-500, $orange-600, $orange-700);
}
&.ci-status-icon-running {
- @include mini-pipeline-graph-color($blue-100, $blue-400, $blue-600);
+ @include mini-pipeline-graph-color($white, $blue-100, $blue-200, $blue-500, $blue-600, $blue-700);
}
&.ci-status-icon-canceled,
@@ -761,42 +800,18 @@
&.ci-status-icon-disabled,
&.ci-status-icon-not-found,
&.ci-status-icon-manual {
- @include mini-pipeline-graph-color(rgba($gl-text-color, 0.1), $gl-text-color, $gl-text-color);
+ @include mini-pipeline-graph-color($white, $gray-700, $gray-800, $gray-900, $gray-950, $black);
}
&.ci-status-icon-created,
&.ci-status-icon-skipped {
- @include mini-pipeline-graph-color(rgba($gray-darkest, 0.1), $gray-darkest, $gray-darkest);
+ @include mini-pipeline-graph-color($white, $gray-200, $gray-300, $gray-500, $gray-600, $gray-700);
}
}
// Dropdown button in mini pipeline graph
button.mini-pipeline-graph-dropdown-toggle {
@include mini-pipeline-item();
-
- > .fa.fa-caret-down {
- position: absolute;
- left: 20px;
- top: 5px;
- display: inline-block;
- visibility: hidden;
- opacity: 0;
- color: inherit;
- font-size: 12px;
- transition: visibility 0.1s, opacity 0.1s linear;
- }
-
- &:active,
- &:focus,
- &:hover {
- outline: none;
- width: 35px;
-
- .fa.fa-caret-down {
- visibility: visible;
- opacity: 1;
- }
- }
}
/**
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index efd8b5f5ff3..505f6e036e3 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -1025,8 +1025,10 @@ pre.light-well {
margin: 0;
}
- @include media-breakpoint-up(md) {
- .description {
+ .description {
+ line-height: 1.5;
+
+ @include media-breakpoint-up(md) {
color: $gl-text-color;
}
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a8fc848c879..26cd5dc801f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -177,11 +177,17 @@ class ApplicationController < ActionController::Base
# hide existence of the resource, rather tell them they cannot access it using
# the provided message
status ||= message.present? ? :forbidden : :not_found
+ template =
+ if status == :not_found
+ "errors/not_found"
+ else
+ "errors/access_denied"
+ end
respond_to do |format|
format.any { head status }
format.html do
- render "errors/access_denied",
+ render template,
layout: "errors",
status: status,
locals: { message: message }
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index f073b6de444..b1d224d026f 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -53,6 +53,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def load_projects(finder_params)
+ @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
+ @total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute
+
projects = ProjectsFinder
.new(params: finder_params, current_user: current_user)
.execute
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 778fdda8dbd..9f074690cbc 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -55,6 +55,9 @@ class Explore::ProjectsController < Explore::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def load_projects
+ @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
+ @total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute
+
projects = ProjectsFinder.new(current_user: current_user, params: params)
.execute
.includes(:route, :creator, :group, namespace: [:route, :owner])
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index 042b6b1264f..9b45be6db99 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -18,6 +18,7 @@ class Import::BaseController < ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
+ # deprecated: being replaced by app/services/import/base_service.rb
def find_or_create_namespace(names, owner)
names = params[:target_namespace].presence || names
@@ -32,6 +33,7 @@ class Import::BaseController < ApplicationController
current_user.namespace
end
+ # deprecated: being replaced by app/services/import/base_service.rb
def project_save_error(project)
project.errors.full_messages.join(', ')
end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index d4c26fa0709..34c7dbdc2fe 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -39,28 +39,25 @@ class Import::GithubController < Import::BaseController
end
def create
- repo = client.repo(params[:repo_id].to_i)
- project_name = params[:new_name].presence || repo.name
- namespace_path = params[:target_namespace].presence || current_user.namespace_path
- target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
-
- if can?(current_user, :create_projects, target_namespace)
- project = Gitlab::LegacyGithubImport::ProjectCreator
- .new(repo, project_name, target_namespace, current_user, access_params, type: provider)
- .execute(extra_project_attrs)
-
- if project.persisted?
- render json: ProjectSerializer.new.represent(project)
- else
- render json: { errors: project_save_error(project) }, status: :unprocessable_entity
- end
+ result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider)
+
+ if result[:status] == :success
+ render json: ProjectSerializer.new.represent(result[:project])
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: result[:message] }, status: result[:http_status]
end
end
private
+ def import_params
+ params.permit(permitted_import_params)
+ end
+
+ def permitted_import_params
+ [:repo_id, :new_name, :target_namespace]
+ end
+
def client
@client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
end
@@ -124,10 +121,6 @@ class Import::GithubController < Import::BaseController
{}
end
- def extra_project_attrs
- {}
- end
-
def extra_import_params
{}
end
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index c24bf211760..09a384e89ab 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -21,11 +21,22 @@ class Projects::BadgesController < Projects::ApplicationController
private
+ def badge_layout
+ case params[:style]
+ when 'flat'
+ 'badge'
+ when 'flat-square'
+ 'badge_flat-square'
+ else
+ 'badge'
+ end
+ end
+
def render_badge(badge)
respond_to do |format|
format.html { render_404 }
format.svg do
- render 'badge', locals: { badge: badge.template }
+ render badge_layout, locals: { badge: badge.template }
end
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 21688e54481..e3e60665506 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -178,8 +178,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
def import_csv
- return render_404 unless Feature.enabled?(:issues_import_csv)
-
if uploader = UploadService.new(project, params[:file]).execute
ImportIssuesCsvWorker.perform_async(current_user.id, project.id, uploader.upload.id)
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 8e68014a30d..8bc59d8a305 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -144,7 +144,7 @@ class Projects::MilestonesController < Projects::ApplicationController
def search_params
if request.format.json? && project_group && can?(current_user, :read_group, project_group)
- groups = project_group.self_and_ancestors_ids
+ groups = project_group.self_and_ancestors.select(:id)
end
params.permit(:state).merge(project_ids: @project.id, group_ids: groups)
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index 9c477978f60..fcd54b6106e 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -3,8 +3,8 @@
# Search for milestones
#
# params - Hash
-# project_ids: Array of project ids or single project id.
-# group_ids: Array of group ids or single group id.
+# project_ids: Array of project ids or single project id or ActiveRecord relation.
+# group_ids: Array of group ids or single group id or ActiveRecord relation.
# order - Orders by field default due date asc.
# title - filter by title.
# state - filters by state.
@@ -12,17 +12,13 @@
class MilestonesFinder
include FinderMethods
- attr_reader :params, :project_ids, :group_ids
+ attr_reader :params
def initialize(params = {})
- @project_ids = Array(params[:project_ids])
- @group_ids = Array(params[:group_ids])
@params = params
end
def execute
- return Milestone.none if project_ids.empty? && group_ids.empty?
-
items = Milestone.all
items = by_groups_and_projects(items)
items = by_title(items)
@@ -34,7 +30,7 @@ class MilestonesFinder
private
def by_groups_and_projects(items)
- items.for_projects_and_groups(project_ids, group_ids)
+ items.for_projects_and_groups(params[:project_ids], params[:group_ids])
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index dc6f8ae1a7f..cfdb3c0d719 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -224,8 +224,15 @@ module Ci
before_transition any => [:failed] do |build|
next unless build.project
+ next unless build.deployment
- build.deployment&.drop
+ begin
+ build.deployment.drop!
+ rescue => e
+ Gitlab::Sentry.track_exception(e, extra: { build_id: build.id })
+ end
+
+ true
end
after_transition any => [:failed] do |build|
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index e25be522d68..26bf73f4dd8 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -5,7 +5,8 @@ module Clusters
class Prometheus < ActiveRecord::Base
include PrometheusAdapter
- VERSION = '6.7.3'.freeze
+ VERSION = '6.7.3'
+ READY_STATUS = [:installed, :updating, :updated, :update_errored].freeze
self.table_name = 'clusters_applications_prometheus'
@@ -24,12 +25,8 @@ module Clusters
end
end
- def ready_status
- [:installed]
- end
-
def ready?
- ready_status.include?(status_name)
+ READY_STATUS.include?(status_name)
end
def chart
@@ -55,6 +52,24 @@ module Clusters
)
end
+ def upgrade_command(values)
+ ::Gitlab::Kubernetes::Helm::UpgradeCommand.new(
+ name,
+ version: VERSION,
+ chart: chart,
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files_with_replaced_values(values)
+ )
+ end
+
+ # Returns a copy of files where the values of 'values.yaml'
+ # are replaced by the argument.
+ #
+ # See #values for the data format required
+ def files_with_replaced_values(replaced_values)
+ files.merge('values.yaml': replaced_values)
+ end
+
def prometheus_client
return unless kube_client
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index 0e74cce29b7..a556dd5ad8b 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -77,6 +77,10 @@ module Clusters
def available?
installed? || updated?
end
+
+ def update_in_progress?
+ updating?
+ end
end
end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index a8c9e54f00c..73a27326f6c 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -15,7 +15,7 @@ module CacheMarkdownField
# Increment this number every time the renderer changes its output
CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 12
+ CACHE_COMMONMARK_VERSION = 13
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 4f73beaafc5..68b2353556e 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -3,6 +3,8 @@
class ExternalIssue
include Referable
+ attr_reader :project
+
def initialize(issue_identifier, project)
@issue_identifier, @project = issue_identifier, project
end
@@ -32,12 +34,8 @@ class ExternalIssue
[self.class, to_s].hash
end
- def project
- @project
- end
-
def project_id
- @project.id
+ project.id
end
def to_reference(_from = nil, full: nil)
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 1ebcbcda0d8..b21edce3aad 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -45,7 +45,7 @@ class Milestone < ActiveRecord::Base
groups = groups.compact if groups.is_a? Array
groups = [] if groups.nil?
- where(project: projects).or(where(group: groups))
+ where(project_id: projects).or(where(group_id: groups))
end
scope :order_by_name_asc, -> { order(Arel::Nodes::Ascending.new(arel_table[:title].lower)) }
@@ -191,7 +191,7 @@ class Milestone < ActiveRecord::Base
return STATE_COUNT_HASH unless projects || groups
counts = Milestone
- .for_projects_and_groups(projects&.map(&:id), groups&.map(&:id))
+ .for_projects_and_groups(projects, groups)
.reorder(nil)
.group(:state)
.count
@@ -275,8 +275,7 @@ class Milestone < ActiveRecord::Base
if project
relation = Milestone.for_projects_and_groups([project_id], [project.group&.id])
elsif group
- project_ids = group.projects.map(&:id)
- relation = Milestone.for_projects_and_groups(project_ids, [group.id])
+ relation = Milestone.for_projects_and_groups(group.projects.select(:id), [group.id])
end
title_exists = relation.find_by_title(title)
diff --git a/app/models/project.rb b/app/models/project.rb
index 27be16720b5..15465d9b356 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -331,7 +331,7 @@ class Project < ActiveRecord::Base
ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
- validate :check_limit, on: :create
+ validate :check_personal_projects_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
validate :visibility_level_allowed_by_group, if: -> { changes.has_key?(:visibility_level) }
validate :visibility_level_allowed_as_fork, if: -> { changes.has_key?(:visibility_level) }
@@ -809,18 +809,22 @@ class Project < ActiveRecord::Base
::Gitlab::CurrentSettings.mirror_available
end
- def check_limit
- unless creator.can_create_project? || namespace.kind == 'group'
- projects_limit = creator.projects_limit
+ def check_personal_projects_limit
+ # Since this method is called as validation hook, `creator` might not be
+ # present. Since the validation for that will fail, we can just return
+ # early.
+ return if !creator || creator.can_create_project? ||
+ namespace.kind == 'group'
- if projects_limit == 0
- self.errors.add(:limit_reached, "Personal project creation is not allowed. Please contact your administrator with questions")
+ limit = creator.projects_limit
+ error =
+ if limit.zero?
+ _('Personal project creation is not allowed. Please contact your administrator with questions')
else
- self.errors.add(:limit_reached, "Your project limit is #{projects_limit} projects! Please contact your administrator to increase it")
+ _('Your project limit is %{limit} projects! Please contact your administrator to increase it')
end
- end
- rescue
- self.errors.add(:base, "Can't check your ability to create project")
+
+ self.errors.add(:limit_reached, error % { limit: limit })
end
def visibility_level_allowed_by_group
@@ -1531,7 +1535,7 @@ class Project < ActiveRecord::Base
end
def pages_available?
- Gitlab.config.pages.enabled && !namespace.subgroup?
+ Gitlab.config.pages.enabled
end
def remove_private_deploy_keys
@@ -1602,24 +1606,7 @@ class Project < ActiveRecord::Base
# rubocop: disable CodeReuse/ServiceClass
def after_create_default_branch
- return unless default_branch
-
- # Ensure HEAD points to the default branch in case it is not master
- change_head(default_branch)
-
- if Gitlab::CurrentSettings.default_branch_protection != Gitlab::Access::PROTECTION_NONE && !ProtectedBranch.protected?(self, default_branch)
- params = {
- name: default_branch,
- push_access_levels_attributes: [{
- access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER
- }],
- merge_access_levels_attributes: [{
- access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER
- }]
- }
-
- ProtectedBranches::CreateService.new(self, creator, params).execute(skip_authorization: true)
- end
+ Projects::ProtectDefaultBranchService.new(self).execute
end
# rubocop: enable CodeReuse/ServiceClass
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index a3fa67c72bf..5eba7ddd75c 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -61,7 +61,10 @@ class RemoteMirror < ActiveRecord::Base
timestamp = Time.now
remote_mirror.update!(
- last_update_at: timestamp, last_successful_update_at: timestamp, last_error: nil
+ last_update_at: timestamp,
+ last_successful_update_at: timestamp,
+ last_error: nil,
+ error_notification_sent: false
)
end
@@ -179,6 +182,10 @@ class RemoteMirror < ActiveRecord::Base
project.repository.add_remote(remote_name, remote_url)
end
+ def after_sent_notification
+ update_column(:error_notification_sent, true)
+ end
+
private
def store_credentials
@@ -221,7 +228,8 @@ class RemoteMirror < ActiveRecord::Base
last_error: nil,
last_update_at: nil,
last_successful_update_at: nil,
- update_status: 'finished'
+ update_status: 'finished',
+ error_notification_sent: false
)
end
diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb
index 7b65bd22f54..4744a7c1cc8 100644
--- a/app/serializers/base_serializer.rb
+++ b/app/serializers/base_serializer.rb
@@ -16,11 +16,11 @@ class BaseSerializer
.as_json
end
- def self.entity(entity_class)
- @entity_class ||= entity_class
- end
+ class << self
+ attr_reader :entity_class
- def self.entity_class
- @entity_class
+ def entity(entity_class)
+ @entity_class ||= entity_class
+ end
end
end
diff --git a/app/serializers/merge_request_diff_entity.rb b/app/serializers/merge_request_diff_entity.rb
index 433bfe60474..7e3053e5881 100644
--- a/app/serializers/merge_request_diff_entity.rb
+++ b/app/serializers/merge_request_diff_entity.rb
@@ -24,6 +24,14 @@ class MergeRequestDiffEntity < Grape::Entity
short_sha(merge_request_diff.head_commit_sha)
end
+ expose :base_version_path do |merge_request_diff|
+ project = merge_request.target_project
+
+ next unless project
+
+ merge_request_version_path(project, merge_request, merge_request_diff)
+ end
+
expose :version_path do |merge_request_diff|
start_sha = options[:start_sha]
project = merge_request.target_project
diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb
index e86ca8cf1d0..8a71730d5ec 100644
--- a/app/services/clusters/applications/base_helm_service.rb
+++ b/app/services/clusters/applications/base_helm_service.rb
@@ -45,6 +45,10 @@ module Clusters
def install_command
@install_command ||= app.install_command
end
+
+ def upgrade_command(new_values = "")
+ app.upgrade_command(new_values)
+ end
end
end
end
diff --git a/app/services/import/base_service.rb b/app/services/import/base_service.rb
new file mode 100644
index 00000000000..2683c75e41f
--- /dev/null
+++ b/app/services/import/base_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Import
+ class BaseService < ::BaseService
+ def initialize(client, user, params)
+ @client = client
+ @current_user = user
+ @params = params
+ end
+
+ private
+
+ def find_or_create_namespace(namespace, owner)
+ namespace = params[:target_namespace].presence || namespace
+
+ return current_user.namespace if namespace == owner
+
+ group = Groups::NestedCreateService.new(current_user, group_path: namespace).execute
+
+ group.errors.any? ? current_user.namespace : group
+ rescue => e
+ Gitlab::AppLogger.error(e)
+
+ current_user.namespace
+ end
+
+ def project_save_error(project)
+ project.errors.full_messages.join(', ')
+ end
+
+ def success(project)
+ super().merge(project: project, status: :success)
+ end
+ end
+end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
new file mode 100644
index 00000000000..a2533683da9
--- /dev/null
+++ b/app/services/import/github_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Import
+ class GithubService < Import::BaseService
+ attr_accessor :client
+ attr_reader :params, :current_user
+
+ def execute(access_params, provider)
+ unless authorized?
+ return error('This namespace has already been taken! Please choose another one.', :unprocessable_entity)
+ end
+
+ project = Gitlab::LegacyGithubImport::ProjectCreator
+ .new(repo, project_name, target_namespace, current_user, access_params, type: provider)
+ .execute(extra_project_attrs)
+
+ if project.persisted?
+ success(project)
+ else
+ error(project_save_error(project), :unprocessable_entity)
+ end
+ end
+
+ def repo
+ @repo ||= client.repo(params[:repo_id].to_i)
+ end
+
+ def project_name
+ @project_name ||= params[:new_name].presence || repo.name
+ end
+
+ def namespace_path
+ @namespace_path ||= params[:target_namespace].presence || current_user.namespace_path
+ end
+
+ def target_namespace
+ @target_namespace ||= find_or_create_namespace(namespace_path, current_user.namespace_path)
+ end
+
+ def extra_project_attrs
+ {}
+ end
+
+ def authorized?
+ can?(current_user, :create_projects, target_namespace)
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index c7e7bb55e4b..805bb5b510d 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -61,10 +61,10 @@ class IssuableBaseService < BaseService
return unless milestone_id
params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE
- group_ids = project.group&.self_and_ancestors&.pluck(:id)
+ groups = project.group&.self_and_ancestors&.select(:id)
milestone =
- Milestone.for_projects_and_groups([project.id], group_ids).find_by_id(milestone_id)
+ Milestone.for_projects_and_groups([project.id], groups).find_by_id(milestone_id)
params[:milestone_id] = '' unless milestone
end
diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb
index 39071b5dc14..cbe5996e8ca 100644
--- a/app/services/milestones/promote_service.rb
+++ b/app/services/milestones/promote_service.rb
@@ -82,11 +82,9 @@ module Milestones
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def group_project_ids
- @group_project_ids ||= group.projects.pluck(:id)
+ group.projects.select(:id)
end
- # rubocop: enable CodeReuse/ActiveRecord
def raise_error(message)
raise PromoteMilestoneError, "Promotion failed - #{message}"
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 61f6402a810..3dad90188cf 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -14,7 +14,7 @@ module Projects
order: { due_date: :asc, title: :asc }
}
- finder_params[:group_ids] = @project.group.self_and_ancestors_ids if @project.group
+ finder_params[:group_ids] = @project.group.self_and_ancestors.select(:id) if @project.group
MilestonesFinder.new(finder_params).execute.select([:iid, :title])
end
diff --git a/app/services/projects/protect_default_branch_service.rb b/app/services/projects/protect_default_branch_service.rb
new file mode 100644
index 00000000000..245490791bf
--- /dev/null
+++ b/app/services/projects/protect_default_branch_service.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Projects
+ # Service class that can be used to execute actions necessary after creating a
+ # default branch.
+ class ProtectDefaultBranchService
+ attr_reader :project, :default_branch_protection
+
+ # @param [Project] project
+ def initialize(project)
+ @project = project
+
+ @default_branch_protection = Gitlab::Access::BranchProtection
+ .new(Gitlab::CurrentSettings.default_branch_protection)
+ end
+
+ def execute
+ protect_default_branch if default_branch
+ end
+
+ def protect_default_branch
+ # Ensure HEAD points to the default branch in case it is not master
+ project.change_head(default_branch)
+
+ create_protected_branch if protect_branch?
+ end
+
+ def create_protected_branch
+ params = {
+ name: default_branch,
+ push_access_levels_attributes: [{ access_level: push_access_level }],
+ merge_access_levels_attributes: [{ access_level: merge_access_level }]
+ }
+
+ # The creator of the project is always allowed to create protected
+ # branches, so we skip the authorization check in this service class.
+ ProtectedBranches::CreateService
+ .new(project, project.creator, params)
+ .execute(skip_authorization: true)
+ end
+
+ def protect_branch?
+ default_branch_protection.any? &&
+ !ProtectedBranch.protected?(project, default_branch)
+ end
+
+ def default_branch
+ project.default_branch
+ end
+
+ def push_access_level
+ if default_branch_protection.developer_can_push?
+ Gitlab::Access::DEVELOPER
+ else
+ Gitlab::Access::MAINTAINER
+ end
+ end
+
+ def merge_access_level
+ if default_branch_protection.developer_can_merge?
+ Gitlab::Access::DEVELOPER
+ else
+ Gitlab::Access::MAINTAINER
+ end
+ end
+ end
+end
diff --git a/app/views/clusters/clusters/_integration_form.html.haml b/app/views/clusters/clusters/_integration_form.html.haml
index 5e451f60c9d..4c47e11927e 100644
--- a/app/views/clusters/clusters/_integration_form.html.haml
+++ b/app/views/clusters/clusters/_integration_form.html.haml
@@ -13,19 +13,19 @@
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
.form-text.text-muted= s_('ClusterIntegration|Enable or disable GitLab\'s connection to your Kubernetes cluster.')
- - if has_multiple_clusters?
- .form-group
- %h5= s_('ClusterIntegration|Environment scope')
+ .form-group
+ %h5= s_('ClusterIntegration|Environment scope')
+ - if has_multiple_clusters?
= field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
.form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
+ - else
+ = text_field_tag :environment_scope, '*', class: 'col-md-6 form-control disabled', placeholder: s_('ClusterIntegration|Environment scope'), disabled: true
+ - environment_scope_url = 'https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope-premium'
+ - environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
+ .form-text.text-muted
+ %code *
+ = s_("ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe }
- if can?(current_user, :update_cluster, @cluster)
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
-
- - unless has_multiple_clusters?
- %h5= s_('ClusterIntegration|Environment scope')
- %p
- %code *
- is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster.
- = link_to 'More information', ('https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope')
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 1050945b15a..ae67192cbcd 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -15,9 +15,11 @@
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your projects
+ %span.badge.badge-pill= limited_counter_with_delimiter(@total_user_projects_count)
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, data: {placement: 'right'} do
Starred projects
+ %span.badge.badge-pill= limited_counter_with_delimiter(@total_starred_projects_count)
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, data: {placement: 'right'} do
Explore projects
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index d62cbc1684b..4b67069d9ac 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -43,7 +43,7 @@
- if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths) do
- = link_to project_tree_path(@project), class: 'shortcuts-tree' do
+ = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
.nav-icon-container
= sprite_icon('doc-text')
%span.nav-item-name
@@ -64,7 +64,7 @@
= _('Commits')
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project) do
+ = link_to project_branches_path(@project), class: 'qa-branches-link' do
= _('Branches')
= nav_link(controller: [:tags]) do
@@ -201,7 +201,7 @@
- if project_nav_tab? :operations
= nav_link(controller: sidebar_operations_paths) do
- = link_to sidebar_operations_link_path, class: 'shortcuts-operations' do
+ = link_to sidebar_operations_link_path, class: 'shortcuts-operations qa-link-operations' do
.nav-icon-container
= sprite_icon('cloud-gear')
%span.nav-item-name
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index e167e094240..ee2c5a13b8a 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,37 +1,37 @@
-- page_title "Account"
+- page_title _('Account')
- @content_class = "limit-container-width" unless fluid_layout
- if current_user.ldap_user?
.alert.alert-info
- Some options are unavailable for LDAP accounts
+ = s_('Profiles|Some options are unavailable for LDAP accounts')
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Two-Factor Authentication
+ = s_('Profiles|Two-Factor Authentication')
%p
- Increase your account's security by enabling Two-Factor Authentication (2FA).
+ = s_("Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)")
.col-lg-8
%p
- Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
+ #{_('Status')}: #{current_user.two_factor_enabled? ? _('Enabled') : _('Disabled')}
- if current_user.two_factor_enabled?
- = link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info'
+ = link_to _('Manage two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-info'
- else
.append-bottom-10
- = link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success'
+ = link_to _('Enable two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-success'
%hr
- if display_providers_on_profile?
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Social sign-in
+ = s_('Profiles|Social sign-in')
%p
- Activate signin with one of the following services
+ = s_('Profiles|Activate signin with one of the following services')
.col-lg-8
%label.label-bold
- Connected Accounts
- %p Click on icon to activate signin with one of the following services
+ = s_('Profiles|Connected Accounts')
+ %p= s_('Profiles|Click on icon to activate signin with one of the following services')
- button_based_providers.each do |provider|
.provider-btn-group
.provider-btn-image
@@ -39,24 +39,24 @@
- if auth_active?(provider)
- if unlink_allowed?(provider)
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
- Disconnect
+ = s_('Profiles|Disconnect')
- else
%a.provider-btn
- Active
+ = s_('Profiles|Active')
- else
= link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
- Connect
+ = s_('Profiles|Connect')
= render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: local_assigns[:group_saml_identities]
%hr
- if current_user.can_change_username?
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0.warning-title
- Change username
+ = s_('Profiles|Change username')
%p
- Changing your username can have unintended side effects.
+ = s_('Profiles|Changing your username can have unintended side effects.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
+ = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
.col-lg-8
- data = { initial_username: current_user.username, root_url: root_url, action_url: update_username_profile_path(format: :json) }
#update-username{ data: data }
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index cfb91568061..f42d5128715 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -14,4 +14,4 @@
= link_to path_to_file, class: 'str-truncated' do
%span= blob.name
%td
- = number_to_human_size(blob.size, precision: 2)
+ = number_to_human_size(blob.size)
diff --git a/app/views/projects/badges/badge_flat-square.svg.erb b/app/views/projects/badges/badge_flat-square.svg.erb
new file mode 100644
index 00000000000..5b90da15ef5
--- /dev/null
+++ b/app/views/projects/badges/badge_flat-square.svg.erb
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="<%= badge.width %>" height="20">
+ <g shape-rendering="crispEdges">
+ <path fill="<%= badge.key_color %>" d="M0 0 h<%= badge.key_width %> v20 H0 z"/>
+ <path fill="<%= badge.value_color %>" d="M<%= badge.key_width %> 0 h<%= badge.value_width %> v20 H<%= badge.key_width %> z"/>
+ </g>
+
+ <g fill="#fff" text-anchor="middle">
+ <g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
+ <text x="<%= badge.key_text_anchor %>" y="14">
+ <%= badge.key_text %>
+ </text>
+ <text x="<%= badge.value_text_anchor %>" y="14">
+ <%= badge.value_text %>
+ </text>
+ </g>
+ </g>
+</svg>
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 88f9b7dfc9f..4b0ea15335e 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -76,7 +76,7 @@
= icon("trash-o")
- else
= link_to project_branch_path(@project, branch.name),
- class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
+ class: "btn btn-remove remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip",
title: s_('Branches|Delete branch'),
method: :delete,
data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } },
diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml
index 0e4b119bb54..93061452e12 100644
--- a/app/views/projects/branches/_panel.html.haml
+++ b/app/views/projects/branches/_panel.html.haml
@@ -10,7 +10,7 @@
.card.prepend-top-10
.card-header
= panel_title
- %ul.content-list.all-branches
+ %ul.content-list.all-branches.qa-all-branches
- branches.first(overview_max_branches).each do |branch|
= render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch)
- if branches.size > overview_max_branches
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index ca867961f6b..43f1cd01b67 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -35,7 +35,7 @@
- if can? current_user, :push_code, @project
= link_to project_merged_branches_path(@project),
- class: 'btn btn-inverted btn-remove has-tooltip',
+ class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches',
title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref },
method: :delete,
data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'),
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index fd6559e37ba..329efa0cdbf 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -1,5 +1,5 @@
- show_feed_buttons = local_assigns.fetch(:show_feed_buttons, true)
-- show_import_button = local_assigns.fetch(:show_import_button, true) && Feature.enabled?(:issues_import_csv) && can?(current_user, :import_issues, @project)
+- show_import_button = local_assigns.fetch(:show_import_button, true) && can?(current_user, :import_issues, @project)
- show_export_button = local_assigns.fetch(:show_export_button, true)
.nav-controls.issues-nav-controls
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index ff9a7b53a86..aaf9b973cda 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -6,75 +6,75 @@
- if project_search_tabs?(:blobs)
%li{ class: active_when(@scope == 'blobs') }
= link_to search_filter_path(scope: 'blobs') do
- Code
+ = _("Code")
%span.badge.badge-pill
= @search_results.blobs_count
- if project_search_tabs?(:issues)
%li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
- Issues
+ = _("Issues")
%span.badge.badge-pill
= limited_count(@search_results.limited_issues_count)
- if project_search_tabs?(:merge_requests)
%li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
- Merge requests
+ = _("Merge requests")
%span.badge.badge-pill
= limited_count(@search_results.limited_merge_requests_count)
- if project_search_tabs?(:milestones)
%li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
- Milestones
+ = _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
- if project_search_tabs?(:notes)
%li{ class: active_when(@scope == 'notes') }
= link_to search_filter_path(scope: 'notes') do
- Comments
+ = _("Comments")
%span.badge.badge-pill
= limited_count(@search_results.limited_notes_count)
- if project_search_tabs?(:wiki)
%li{ class: active_when(@scope == 'wiki_blobs') }
= link_to search_filter_path(scope: 'wiki_blobs') do
- Wiki
+ = _("Wiki")
%span.badge.badge-pill
= @search_results.wiki_blobs_count
- if project_search_tabs?(:commits)
%li{ class: active_when(@scope == 'commits') }
= link_to search_filter_path(scope: 'commits') do
- Commits
+ = _("Commits")
%span.badge.badge-pill
= @search_results.commits_count
- elsif @show_snippets
%li{ class: active_when(@scope == 'snippet_blobs') }
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
- Snippet Contents
+ = _("Snippet Contents")
%span.badge.badge-pill
= @search_results.snippet_blobs_count
%li{ class: active_when(@scope == 'snippet_titles') }
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
- Titles and Filenames
+ = _("Titles and Filenames")
%span.badge.badge-pill
= @search_results.snippet_titles_count
- else
%li{ class: active_when(@scope == 'projects') }
= link_to search_filter_path(scope: 'projects') do
- Projects
+ = _("Projects")
%span.badge.badge-pill
= limited_count(@search_results.limited_projects_count)
%li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
- Issues
+ = _("Issues")
%span.badge.badge-pill
= limited_count(@search_results.limited_issues_count)
%li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
- Merge requests
+ = _("Merge requests")
%span.badge.badge-pill
= limited_count(@search_results.limited_merge_requests_count)
%li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
- Milestones
+ = _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 6837f64f132..c8b6a3258ab 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -3,31 +3,31 @@
- if params[:project_id].present?
= hidden_field_tag :project_id, params[:project_id]
.dropdown
- %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:", group_id: params[:group_id] } }
+ %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Group:'), group_id: params[:group_id] } }
%span.dropdown-toggle-text
- Group:
+ = _("Group:")
- if @group.present?
= @group.name
- else
- Any
+ = _("Any")
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right
- = dropdown_title("Filter results by group")
- = dropdown_filter("Search groups")
+ = dropdown_title(_("Filter results by group"))
+ = dropdown_filter(_("Search groups"))
= dropdown_content
= dropdown_loading
.dropdown.project-filter
- %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } }
+ %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Project:') } }
%span.dropdown-toggle-text
- Project:
+ = _("Project:")
- if @project.present?
= @project.full_name
- else
- Any
+ = _("Any")
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right
- = dropdown_title("Filter results by project")
- = dropdown_filter("Search projects")
+ = dropdown_title(_("Filter results by project"))
+ = dropdown_filter(_("Search projects"))
= dropdown_content
= dropdown_loading
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index a4a5cec1314..4af0c6bf84a 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -4,12 +4,12 @@
.search-holder
.search-field-holder
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
+ = search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
= icon("search", class: "search-icon")
%button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" }
= icon("times-circle")
%span.sr-only
- Clear search
+ = _("Clear search")
- unless params[:snippets].eql? 'true'
= render 'filter'
- = button_tag "Search", class: "btn btn-success btn-search"
+ = button_tag _("Search"), class: "btn btn-success btn-search"
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index c4d52431d6e..be7a2436d16 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -6,9 +6,11 @@
= search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
- in project #{link_to @project.full_name, [@project.namespace.becomes(Namespace), @project]}
+ - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project])
+ = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- elsif @group
- in group #{link_to @group.name, @group}
+ - link_to_group = link_to(@group.name, @group)
+ = _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
.results.prepend-top-10
- if @scope == 'commits'
diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml
index 821a39d61f5..9d15995bb51 100644
--- a/app/views/search/results/_empty.html.haml
+++ b/app/views/search/results/_empty.html.haml
@@ -2,5 +2,5 @@
.search_glyph
%h4
= icon('search')
- We couldn't find any results matching
+ = _("We couldn't find any results matching")
%code= @search_term
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index c413c3d4abb..796782035f2 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -4,7 +4,7 @@
= link_to [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
+ %span.badge.badge-danger.prepend-left-5= _("Closed")
.float-right ##{issue.iid}
- if issue.description.present?
.description.term
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 519176af108..f0e0af11f27 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -3,9 +3,9 @@
= link_to [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
+ %span.badge.badge-primary.prepend-left-5= _("Merged")
- elsif merge_request.closed?
- %span.badge.badge-danger.prepend-left-5 Closed
+ %span.badge.badge-danger.prepend-left-5= _("Closed")
.float-right= merge_request.to_reference
- if merge_request.description.present?
.description.term
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index e4ab7b0541f..6717939d034 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -6,14 +6,14 @@
%h5.note-search-caption.str-truncated
%i.fa.fa-comment
= link_to_member(project, note.author, avatar: false)
- commented on
- = link_to project.full_name, project
+ - link_to_project = link_to(project.full_name, project)
+ = _("commented on %{link_to_project}").html_safe % { link_to_project: link_to_project }
&middot;
- if note.for_commit?
- = link_to_if(noteable_identifier, "Commit #{truncate_sha(note.commit_id)}", note_url) do
+ = link_to_if(noteable_identifier, _("Commit %{commit_id}") % { commit_id: truncate_sha(note.commit_id) }, note_url) do
= truncate_sha(note.commit_id)
- %span.light Commit deleted
+ %span.light= _("Commit deleted")
- else
%span #{note.noteable_type.titleize} ##{noteable_identifier}
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index b4ecd7bbae9..e0130f9a4b5 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -24,7 +24,7 @@
= markup(snippet.file_name, chunk[:data], legacy_render_context(params))
- else
.file-content.code
- .nothing-here-block Empty file
+ .nothing-here-block= _("Empty file")
- else
.file-content.code.js-syntax-highlight
.line-numbers
@@ -42,4 +42,4 @@
= highlight(snippet.file_name, chunk[:data])
- else
.file-content.code
- .nothing-here-block Empty file
+ .nothing-here-block= _("Empty file")
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 9c8afb2165b..1e01088d9e6 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -5,7 +5,7 @@
- if snippet_title.private?
%span.badge.badge-gray
%i.fa.fa-lock
- private
+ = _("private")
%span.cgray.monospace.tiny.float-right.term
= snippet_title.file_name
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 499697f2777..3260d05f509 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,5 +1,5 @@
- @hide_top_links = true
-- breadcrumb_title "Search"
+- breadcrumb_title _("Search")
- page_title @search_term
.prepend-top-default
diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml
index 28b34e38b15..8607f87ce0b 100644
--- a/app/views/shared/_mini_pipeline_graph.html.haml
+++ b/app/views/shared/_mini_pipeline_graph.html.haml
@@ -7,7 +7,6 @@
.stage-container.dropdown{ class: klass }
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_ajax_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
= sprite_icon(icon_status)
- = icon('caret-down')
%ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
%li.js-builds-dropdown-list.scrollable-menu
diff --git a/app/views/shared/_personal_access_tokens_created_container.html.haml b/app/views/shared/_personal_access_tokens_created_container.html.haml
index 3150d39b84a..a8d3de66418 100644
--- a/app/views/shared/_personal_access_tokens_created_container.html.haml
+++ b/app/views/shared/_personal_access_tokens_created_container.html.haml
@@ -6,7 +6,7 @@
= container_title
.form-group
.input-group
- = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block"
+ = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "qa-created-personal-access-token form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block"
%span.input-group-append
= clipboard_button(text: new_token_value, title: clipboard_button_title, placement: "left", class: "input-group-text btn-default btn-clipboard")
%span#created-token-help-block.form-text.text-muted.text-danger Make sure you save it - you won't be able to access it again.
diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml
index f4df7bdcd83..0891b3459ec 100644
--- a/app/views/shared/_personal_access_tokens_form.html.haml
+++ b/app/views/shared/_personal_access_tokens_form.html.haml
@@ -12,7 +12,7 @@
.row
.form-group.col-md-6
= f.label :name, class: 'label-bold'
- = f.text_field :name, class: "form-control", required: true
+ = f.text_field :name, class: "form-control qa-personal-access-token-name-field", required: true
.row
.form-group.col-md-6
@@ -26,4 +26,4 @@
= render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes
.prepend-top-default
- = f.submit "Create #{type} token", class: "btn btn-success"
+ = f.submit "Create #{type} token", class: "btn btn-success qa-create-token-button"
diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml
index 2efd03d4867..49f3aae0f98 100644
--- a/app/views/shared/_personal_access_tokens_table.html.haml
+++ b/app/views/shared/_personal_access_tokens_table.html.haml
@@ -29,7 +29,7 @@
%span.token-never-expires-label Never
%td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
- path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token)
- %td= link_to "Revoke", path, method: :put, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." }
+ %td= link_to "Revoke", path, method: :put, class: "btn btn-danger float-right qa-revoke-button", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." }
- else
.settings-message.text-center
This user has no active #{type} Tokens.
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index c6c5cadc3f5..307a0919a4c 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -1,6 +1,6 @@
.board{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
":data-id" => "list.id" }
- .board-inner
+ .board-inner.d-flex.flex-column
%header.board-header{ ":class" => '{ "has-border": list.label && list.label.color }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" }
%h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' }
%i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 2691ec4cd46..9173b802dd4 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -1,6 +1,6 @@
- button_path = local_assigns.fetch(:button_path, false)
- project_select_button = local_assigns.fetch(:project_select_button, false)
-- show_import_button = local_assigns.fetch(:show_import_button, false) && Feature.enabled?(:issues_import_csv) && can?(current_user, :import_issues, @project)
+- show_import_button = local_assigns.fetch(:show_import_button, false) && can?(current_user, :import_issues, @project)
- has_button = button_path || project_select_button
- closed_issues_count = issuables_count_for_state(:issues, :closed)
- opened_issues_count = issuables_count_for_state(:issues, :opened)
diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml
index af9db5f59a8..a5d3e1c8de0 100644
--- a/app/views/shared/tokens/_scopes_form.html.haml
+++ b/app/views/shared/tokens/_scopes_form.html.haml
@@ -4,6 +4,6 @@
- scopes.each do |scope|
%fieldset.form-group.form-check
- = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: 'form-check-input'
+ = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: "form-check-input qa-#{scope}-radio"
= label_tag ("#{prefix}_scopes_#{scope}"), scope, class: 'label-bold form-check-label'
.text-secondary= t scope, scope: [:doorkeeper, :scope_desc]
diff --git a/app/workers/remote_mirror_notification_worker.rb b/app/workers/remote_mirror_notification_worker.rb
index 70c2e857d09..5bafe8e2046 100644
--- a/app/workers/remote_mirror_notification_worker.rb
+++ b/app/workers/remote_mirror_notification_worker.rb
@@ -9,7 +9,10 @@ class RemoteMirrorNotificationWorker
# We check again if there's an error because a newer run since this job was
# fired could've completed successfully.
return unless remote_mirror && remote_mirror.last_error.present?
+ return if remote_mirror.error_notification_sent?
NotificationService.new.remote_mirror_update_failed(remote_mirror)
+
+ remote_mirror.after_sent_notification
end
end
diff --git a/bin/changelog b/bin/changelog
index 758c036161e..328d9495b96 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -148,7 +148,7 @@ class ChangelogEntry
def execute
assert_feature_branch!
- assert_title!
+ assert_title! unless editor
assert_new_file!
# Read type from $stdin unless is already set
@@ -162,6 +162,10 @@ class ChangelogEntry
write
amend_commit if options.amend
end
+
+ if editor
+ system("#{editor} '#{file_path}'")
+ end
end
private
@@ -180,6 +184,10 @@ class ChangelogEntry
File.write(file_path, contents)
end
+ def editor
+ ENV['EDITOR']
+ end
+
def amend_commit
fail_with "git add failed" unless system(*%W[git add #{file_path}])
diff --git a/changelogs/unreleased/26375-markdown-footnotes-not-working.yml b/changelogs/unreleased/26375-markdown-footnotes-not-working.yml
new file mode 100644
index 00000000000..86adef84a2a
--- /dev/null
+++ b/changelogs/unreleased/26375-markdown-footnotes-not-working.yml
@@ -0,0 +1,5 @@
+---
+title: Footnotes now render properly in markdown
+merge_request: 24168
+author:
+type: fixed
diff --git a/changelogs/unreleased/30120-add-flat-square-badge-style.yml b/changelogs/unreleased/30120-add-flat-square-badge-style.yml
new file mode 100644
index 00000000000..a542a58d3fc
--- /dev/null
+++ b/changelogs/unreleased/30120-add-flat-square-badge-style.yml
@@ -0,0 +1,5 @@
+---
+title: Add flat-square badge style
+merge_request: 24172
+author: Fabian Schneider @fabsrc
+type: added
diff --git a/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml b/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml
new file mode 100644
index 00000000000..5a4ff8b3358
--- /dev/null
+++ b/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml
@@ -0,0 +1,5 @@
+---
+title: Remove expansion hover animation from pipeline status icon buttons
+merge_request: 24268
+author: Nathan Friend
+type: changed
diff --git a/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml b/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml
new file mode 100644
index 00000000000..d1a80ab43cf
--- /dev/null
+++ b/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml
@@ -0,0 +1,5 @@
+---
+title: Improve milestone queries using subqueries instead of separate queries for ids
+merge_request: 24325
+author:
+type: performance
diff --git a/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml b/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml
new file mode 100644
index 00000000000..c1cde0ceff6
--- /dev/null
+++ b/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve In Merge Request diff screen, master is not a hyperlink
+merge_request: 23874
+author:
+type: fixed
diff --git a/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml b/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml
new file mode 100644
index 00000000000..07cb35e6529
--- /dev/null
+++ b/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml
@@ -0,0 +1,5 @@
+---
+title: Modifies environment scope UI on cluster page
+merge_request: 24376
+author:
+type: other
diff --git a/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml b/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml
new file mode 100644
index 00000000000..b609fc2d60b
--- /dev/null
+++ b/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Fix default_branch_protection admin setting'
+merge_request: 24398
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/55945-suggested-change-highlight.yml b/changelogs/unreleased/55945-suggested-change-highlight.yml
deleted file mode 100644
index 611854d36ab..00000000000
--- a/changelogs/unreleased/55945-suggested-change-highlight.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add syntax highlighting to suggestion diff
-merge_request: 24156
-author:
-type: fixed
diff --git a/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml b/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml
new file mode 100644
index 00000000000..7c923422534
--- /dev/null
+++ b/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml
@@ -0,0 +1,6 @@
+---
+title: Show CI artifact file size with 3 significant digits on 'browse job artifacts'
+ page
+merge_request: 24387
+author:
+type: fixed
diff --git a/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml b/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml
new file mode 100644
index 00000000000..49511294c48
--- /dev/null
+++ b/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml
@@ -0,0 +1,5 @@
+---
+title: Proper align Projects dropdown on issue boards page
+merge_request: 24277
+author: Johann Hubert Sonntagbauer
+type: fixed
diff --git a/changelogs/unreleased/ac-pages-subgroups.yml b/changelogs/unreleased/ac-pages-subgroups.yml
new file mode 100644
index 00000000000..ef5a0c1872e
--- /dev/null
+++ b/changelogs/unreleased/ac-pages-subgroups.yml
@@ -0,0 +1,5 @@
+---
+title: Pages for subgroups
+merge_request: 23505
+author:
+type: added
diff --git a/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml b/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml
new file mode 100644
index 00000000000..e200bbaa806
--- /dev/null
+++ b/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml
@@ -0,0 +1,5 @@
+---
+title: Add badge count to projects
+merge_request: 18425
+author: George Tsiolis
+type: added
diff --git a/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml b/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml
new file mode 100644
index 00000000000..5365260cbae
--- /dev/null
+++ b/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid overwriting default jaeger values with nil
+merge_request: 24482
+author:
+type: fixed
diff --git a/changelogs/unreleased/an-gilab-process-name.yml b/changelogs/unreleased/an-gilab-process-name.yml
new file mode 100644
index 00000000000..72d811ee21f
--- /dev/null
+++ b/changelogs/unreleased/an-gilab-process-name.yml
@@ -0,0 +1,5 @@
+---
+title: Extract process_name from GitLab::Sentry
+merge_request: 24422
+author:
+type: other
diff --git a/changelogs/unreleased/an-opentracing-factory.yml b/changelogs/unreleased/an-opentracing-factory.yml
new file mode 100644
index 00000000000..c04736f3e63
--- /dev/null
+++ b/changelogs/unreleased/an-opentracing-factory.yml
@@ -0,0 +1,5 @@
+---
+title: Conditionally initialize the global opentracing tracer
+merge_request: 24186
+author:
+type: other
diff --git a/changelogs/unreleased/api-wiki-dot-slug.yml b/changelogs/unreleased/api-wiki-dot-slug.yml
new file mode 100644
index 00000000000..82c76fa7450
--- /dev/null
+++ b/changelogs/unreleased/api-wiki-dot-slug.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Support dots in wiki slugs'
+merge_request: 24383
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/auto-devops-custom-domains.yml b/changelogs/unreleased/auto-devops-custom-domains.yml
new file mode 100644
index 00000000000..37e8ee26a4d
--- /dev/null
+++ b/changelogs/unreleased/auto-devops-custom-domains.yml
@@ -0,0 +1,5 @@
+---
+title: Added support for custom hosts/domains to Auto DevOps
+merge_request: 24248
+author: walkafwalka
+type: added
diff --git a/changelogs/unreleased/backup_restore_fix_issue_46891.yml b/changelogs/unreleased/backup_restore_fix_issue_46891.yml
new file mode 100644
index 00000000000..b8fe3b1b861
--- /dev/null
+++ b/changelogs/unreleased/backup_restore_fix_issue_46891.yml
@@ -0,0 +1,5 @@
+---
+title: Modify file restore to rectify tar issue
+merge_request: 24000
+author:
+type: fixed
diff --git a/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml b/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml
new file mode 100644
index 00000000000..6e8dac97249
--- /dev/null
+++ b/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Cleanup legacy artifact background migration
+merge_request: 24144
+author:
+type: other
diff --git a/changelogs/unreleased/diff-tree-collapse-directories.yml b/changelogs/unreleased/diff-tree-collapse-directories.yml
new file mode 100644
index 00000000000..6eae48f2352
--- /dev/null
+++ b/changelogs/unreleased/diff-tree-collapse-directories.yml
@@ -0,0 +1,5 @@
+---
+title: Collapse directory structure in merge request file tree
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml b/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml
new file mode 100644
index 00000000000..b224cace4bf
--- /dev/null
+++ b/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml
@@ -0,0 +1,5 @@
+---
+title: Document graphicsmagick installation for source installation
+merge_request: 24404
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml b/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml
new file mode 100644
index 00000000000..eda69b32094
--- /dev/null
+++ b/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml
@@ -0,0 +1,5 @@
+---
+title: Show the correct error page when access is denied
+merge_request: 23932
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-runner-eternal-loop-when-update-job-result.yml b/changelogs/unreleased/fix-runner-eternal-loop-when-update-job-result.yml
new file mode 100644
index 00000000000..5a6c36e6f5f
--- /dev/null
+++ b/changelogs/unreleased/fix-runner-eternal-loop-when-update-job-result.yml
@@ -0,0 +1,5 @@
+---
+title: Fix runner eternal loop when update job result
+merge_request: 24481
+author:
+type: fixed
diff --git a/changelogs/unreleased/gitaly-update-1-13-0.yml b/changelogs/unreleased/gitaly-update-1-13-0.yml
new file mode 100644
index 00000000000..73de25a532d
--- /dev/null
+++ b/changelogs/unreleased/gitaly-update-1-13-0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Gitaly to 1.13.0
+merge_request: 24429
+author:
+type: other
diff --git a/changelogs/unreleased/homepage-proj-descr-cutoff.yml b/changelogs/unreleased/homepage-proj-descr-cutoff.yml
new file mode 100644
index 00000000000..837c01f6722
--- /dev/null
+++ b/changelogs/unreleased/homepage-proj-descr-cutoff.yml
@@ -0,0 +1,5 @@
+---
+title: Increase line height of project summaries
+merge_request:
+author: gfyoung
+type: fixed
diff --git a/changelogs/unreleased/security-2770-verify-bundle-import-files.yml b/changelogs/unreleased/security-2770-verify-bundle-import-files.yml
new file mode 100644
index 00000000000..dea40dd1ef1
--- /dev/null
+++ b/changelogs/unreleased/security-2770-verify-bundle-import-files.yml
@@ -0,0 +1,5 @@
+---
+title: Validate bundle files before unpacking them
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
new file mode 100644
index 00000000000..d1d4412eb50
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Fix duplicate project disk path in BackfillLegacyProjectRepositories
+merge_request: 24213
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-fix-gon-helper-avatar.yml b/changelogs/unreleased/sh-fix-gon-helper-avatar.yml
deleted file mode 100644
index c83273608ad..00000000000
--- a/changelogs/unreleased/sh-fix-gon-helper-avatar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix no avatar not showing in user selection box
-merge_request: 24346
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-real-size-warnings.yml b/changelogs/unreleased/sh-fix-real-size-warnings.yml
deleted file mode 100644
index 5062ffd677c..00000000000
--- a/changelogs/unreleased/sh-fix-real-size-warnings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broken templated "Too many changes to show" text
-merge_request: 24282
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-request-profiles-html.yml b/changelogs/unreleased/sh-fix-request-profiles-html.yml
deleted file mode 100644
index 74e4115db8e..00000000000
--- a/changelogs/unreleased/sh-fix-request-profiles-html.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix requests profiler in admin page not rendering HTML properly
-merge_request: 24291
-author:
-type: fixed
diff --git a/changelogs/unreleased/shared_with_group_path.yml b/changelogs/unreleased/shared_with_group_path.yml
new file mode 100644
index 00000000000..73ba9a9f30a
--- /dev/null
+++ b/changelogs/unreleased/shared_with_group_path.yml
@@ -0,0 +1,5 @@
+---
+title: Add group full path to project's shared_with_groups
+merge_request: 24052
+author: Mathieu Parent
+type: added
diff --git a/changelogs/unreleased/suggestion-dashes.yml b/changelogs/unreleased/suggestion-dashes.yml
deleted file mode 100644
index e99ab30b263..00000000000
--- a/changelogs/unreleased/suggestion-dashes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed diff suggestions removing dashes
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml b/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml
new file mode 100644
index 00000000000..1af1fe09f33
--- /dev/null
+++ b/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml
@@ -0,0 +1,5 @@
+---
+title: 'i18n: externalize strings from ''app/views/search'''
+merge_request: 24297
+author: Tao Wang
+type: other
diff --git a/changelogs/unreleased/update-sidekiq-cron.yml b/changelogs/unreleased/update-sidekiq-cron.yml
new file mode 100644
index 00000000000..edce32e3753
--- /dev/null
+++ b/changelogs/unreleased/update-sidekiq-cron.yml
@@ -0,0 +1,6 @@
+---
+title: Update sidekiq-cron to 1.0.4 and use fugit to replace rufus-scheduler to parse
+ cron syntax
+merge_request: 24235
+author:
+type: other
diff --git a/changelogs/unreleased/zj-feature-gate-set-project-path.yml b/changelogs/unreleased/zj-feature-gate-set-project-path.yml
new file mode 100644
index 00000000000..b426a2f3fe7
--- /dev/null
+++ b/changelogs/unreleased/zj-feature-gate-set-project-path.yml
@@ -0,0 +1,5 @@
+---
+title: Allow setting of feature gates per project
+merge_request: 24184
+author:
+type: added
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index 2a6c5148f71..abc91c3ae51 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -19,7 +19,7 @@ def configure_sentry
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]
- config.tags = { program: Gitlab::Sentry.program_context }
+ config.tags = { program: Gitlab.process_name }
end
end
end
diff --git a/config/initializers/tracing.rb b/config/initializers/tracing.rb
new file mode 100644
index 00000000000..be95f30d075
--- /dev/null
+++ b/config/initializers/tracing.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+if Gitlab::Tracing.enabled?
+ require 'opentracing'
+
+ # In multi-processed clustered architectures (puma, unicorn) don't
+ # start tracing until the worker processes are spawned. This works
+ # around issues when the opentracing implementation spawns threads
+ Gitlab::Cluster::LifecycleEvents.on_worker_start do
+ tracer = Gitlab::Tracing::Factory.create_tracer(Gitlab.process_name, Gitlab::Tracing.connection_string)
+ OpenTracing.global_tracer = tracer if tracer
+ end
+end
diff --git a/db/migrate/20190104182041_cleanup_legacy_artifact_migration.rb b/db/migrate/20190104182041_cleanup_legacy_artifact_migration.rb
new file mode 100644
index 00000000000..11659846a06
--- /dev/null
+++ b/db/migrate/20190104182041_cleanup_legacy_artifact_migration.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class CleanupLegacyArtifactMigration < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class Build < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'ci_builds'
+ self.inheritance_column = :_type_disabled
+
+ scope :with_legacy_artifacts, -> { where("artifacts_file <> ''") }
+ end
+
+ def up
+ Gitlab::BackgroundMigration.steal('MigrateLegacyArtifacts')
+
+ CleanupLegacyArtifactMigration::Build
+ .with_legacy_artifacts
+ .each_batch(of: 100) do |batch|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ Gitlab::BackgroundMigration::MigrateLegacyArtifacts.new.perform(*range)
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190108192941_remove_partial_index_from_ci_builds_artifacts_file.rb b/db/migrate/20190108192941_remove_partial_index_from_ci_builds_artifacts_file.rb
new file mode 100644
index 00000000000..073faf721ae
--- /dev/null
+++ b/db/migrate/20190108192941_remove_partial_index_from_ci_builds_artifacts_file.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemovePartialIndexFromCiBuildsArtifactsFile < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'partial_index_ci_builds_on_id_with_legacy_artifacts'.freeze
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:ci_builds, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:ci_builds, :id, where: "artifacts_file <> ''", name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb b/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb
new file mode 100644
index 00000000000..d8f979a1848
--- /dev/null
+++ b/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddErrorNotificationSentToRemoteMirrors < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :remote_mirrors, :error_notification_sent, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 87826881d58..c6fef9b5d11 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190103140724) do
+ActiveRecord::Schema.define(version: 20190115054216) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -354,7 +354,6 @@ ActiveRecord::Schema.define(version: 20190103140724) do
t.index ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
t.index ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
t.index ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
- t.index ["id"], name: "partial_index_ci_builds_on_id_with_legacy_artifacts", where: "(artifacts_file <> ''::text)", using: :btree
t.index ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree
t.index ["project_id", "status"], name: "index_ci_builds_project_id_and_status_for_live_jobs_partial2", where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))", using: :btree
t.index ["protected"], name: "index_ci_builds_on_protected", using: :btree
@@ -1849,6 +1848,7 @@ ActiveRecord::Schema.define(version: 20190103140724) do
t.string "encrypted_credentials_salt"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.boolean "error_notification_sent"
t.index ["last_successful_update_at"], name: "index_remote_mirrors_on_last_successful_update_at", using: :btree
t.index ["project_id"], name: "index_remote_mirrors_on_project_id", using: :btree
end
diff --git a/doc/api/avatar.md b/doc/api/avatar.md
index aa6f7c185ae..e55fffba4b2 100644
--- a/doc/api/avatar.md
+++ b/doc/api/avatar.md
@@ -1,33 +1,41 @@
# Avatar API
-> [Introduced][ce-19121] in GitLab 11.0
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19121) in GitLab 11.0.
## Get a single avatar URL
-Get a single avatar URL for a given email address. If user with matching public
-email address is not found, results from external avatar services are returned.
-This endpoint can be accessed without authentication. In case public visibility
-is restricted, response will be `403 Forbidden` when unauthenticated.
+Get a single [avatar](../user/profile/index.md#profile-settings) URL for a user with the given email address.
-```
+If:
+
+- No user with the given public email address is found, results from external avatar services are
+ returned.
+- Public visibility is restricted, response will be `403 Forbidden` when unauthenticated.
+
+NOTE: **Note:**
+This endpoint can be accessed without authentication.
+
+```text
GET /avatar?email=admin@example.com
```
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | --------------------- |
-| `email` | string | yes | Public email address of the user |
-| `size` | integer | no | Single pixel dimension (since images are squares). Only used for avatar lookups at `Gravatar` or at the configured `Libravatar` server |
+Parameters:
-```bash
-curl https://gitlab.example.com/api/v4/avatar?email=admin@example.com
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
+| `email` | string | yes | Public email address of the user. |
+| `size` | integer | no | Single pixel dimension (since images are squares). Only used for avatar lookups at `Gravatar` or at the configured `Libravatar` server. |
+
+Example request:
+
+```sh
+curl https://gitlab.example.com/api/v4/avatar?email=admin@example.com&size=32
```
Example response:
```json
{
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon"
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=64&d=identicon"
}
```
-
-[ce-19121]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19121
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 41e39c31069..92936a277ac 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -1,19 +1,26 @@
# Award Emoji API
-> [Introduced][ce-4575] in GitLab 8.9, Snippet support in 8.12
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575) in GitLab 8.9. Snippet support added in 8.12.
+An [awarded emoji](../user/award_emojis.md) tells a thousand words.
-An awarded emoji tells a thousand words, and can be awarded on issues, merge
-requests, snippets, and notes/comments. Issues, merge requests, snippets, and notes are further called
-`awardables`.
+Emoji can be awarded on the following (known as "awardables"):
+
+- [Issues](../user/project/issues/index.md) ([API](issues.md)).
+- [Merge requests](../user/project/merge_requests/index.md) ([API](merge_requests.md)).
+- [Snippets](../user/snippets.md) ([API](snippets.md)).
+
+Emoji can also [be awarded](../user/award_emojis.html#award-emoji-for-comments) on comments (also known as notes). See also [Notes API](notes.md).
## Issues, merge requests, and snippets
+See [Award Emoji on Comments](#award-emoji-on-comments) for information on using these endpoints with comments.
+
### List an awardable's award emoji
-Gets a list of all award emoji
+Get a list of all award emoji for a specified awardable.
-```
+```text
GET /projects/:id/issues/:issue_iid/award_emoji
GET /projects/:id/merge_requests/:merge_request_iid/award_emoji
GET /projects/:id/snippets/:snippet_id/award_emoji
@@ -21,16 +28,18 @@ GET /projects/:id/snippets/:snippet_id/award_emoji
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
-```bash
+Example request:
+
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji
```
-Example Response:
+Example response:
```json
[
@@ -71,9 +80,9 @@ Example Response:
### Get single award emoji
-Gets a single award emoji from an issue, snippet, or merge request.
+Get a single award emoji from an issue, snippet, or merge request.
-```
+```text
GET /projects/:id/issues/:issue_iid/award_emoji/:award_id
GET /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
@@ -81,17 +90,19 @@ GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
-| `award_id` | integer | yes | The ID of the award emoji |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `award_id` | integer | yes | ID of the award emoji. |
+
+Example request:
-```bash
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/1
```
-Example Response:
+Example response:
```json
{
@@ -114,9 +125,9 @@ Example Response:
### Award a new emoji
-This end point creates an award emoji on the specified resource
+Create an award emoji on the specified awardable.
-```
+```text
POST /projects/:id/issues/:issue_iid/award_emoji
POST /projects/:id/merge_requests/:merge_request_iid/award_emoji
POST /projects/:id/snippets/:snippet_id/award_emoji
@@ -124,13 +135,13 @@ POST /projects/:id/snippets/:snippet_id/award_emoji
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
-| `name` | string | yes | The name of the emoji, without colons |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `name` | string | yes | Name of the emoji without colons. |
-```bash
+```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji?name=blowfish
```
@@ -157,10 +168,12 @@ Example Response:
### Delete an award emoji
-Sometimes its just not meant to be, and you'll have to remove your award. Only available to
-admins or the author of the award.
+Sometimes it's just not meant to be and you'll have to remove the award.
-```
+NOTE: **Note:**
+Only available to administrators or the author of the award.
+
+```text
DELETE /projects/:id/issues/:issue_iid/award_emoji/:award_id
DELETE /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
@@ -168,43 +181,47 @@ DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `award_id` | integer | yes | The ID of an award_emoji |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `award_id` | integer | yes | ID of an award emoji. |
-```bash
+```sh
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/344
```
-## Award Emoji on Notes
+## Award Emoji on Comments
-The endpoints documented above are available for Notes as well. Notes
-are a sub-resource of Issues, Merge Requests, or Snippets. The examples below
-describe working with Award Emoji on notes for an Issue, but can be
-easily adapted for notes on a Merge Request.
+Comments (also known as notes) are a sub-resource of issues, merge requests, and snippets.
-### List a note's award emoji
+NOTE: **Note:**
+The examples below describe working with award emoji on comments for an issue, but can be
+easily adapted for comments on a merge request.
-```
+### List a comment's award emoji
+
+Get all award emoji for a comment (note).
+
+```text
GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+Example request:
-```bash
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji
```
-Example Response:
+Example response:
```json
[
@@ -227,26 +244,30 @@ Example Response:
]
```
-### Get single note's award emoji
+### Get an award emoji for a comment
-```
+Get a single award emoji for a comment (note).
+
+```text
GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of the award emoji |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `award_id` | integer | yes | ID of the award emoji. |
+
+Example request:
-```bash
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji/2
```
-Example Response:
+Example response:
```json
{
@@ -267,26 +288,30 @@ Example Response:
}
```
-### Award a new emoji on a note
+### Award a new emoji on a comment
-```
+Create an award emoji on the specified comment (note).
+
+```text
POST /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `name` | string | yes | The name of the emoji, without colons |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `name` | string | yes | Name of the emoji without colons. |
-```bash
+Example request:
+
+```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji?name=rocket
```
-Example Response:
+Example response:
```json
{
@@ -307,26 +332,28 @@ Example Response:
}
```
-### Delete an award emoji
+### Delete an award emoji from a comment
-Sometimes its just not meant to be, and you'll have to remove your award. Only available to
-admins or the author of the award.
+Sometimes it's just not meant to be and you'll have to remove the award.
-```
+NOTE: **Note:**
+Only available to administrators or the author of the award.
+
+```text
DELETE /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of an award_emoji |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `award_id` | integer | yes | ID of an award_emoji. |
+
+Example request:
-```bash
+```sh
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/345
```
-
-[ce-4575]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 3b55154887d..8d5f333ba77 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -1,21 +1,31 @@
# Branches API
+This API operates on [repository branches](../user/project/repository/branches/index.md).
+
+TIP: **Tip:**
+See also [Protected branches API](protected_branches.md).
+
## List repository branches
Get a list of repository branches from a project, sorted by name alphabetically.
-This endpoint can be accessed without authentication if the repository is
-publicly accessible.
-```
+NOTE: **Note:**
+This endpoint can be accessed without authentication if the repository is publicly accessible.
+
+```text
GET /projects/:id/repository/branches
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `search` | string | no | Return list of branches matching the search criteria. |
+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. |
+| `search` | string | no | Return list of branches matching the search criteria. |
+
+Example request:
-```bash
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches
```
@@ -53,19 +63,25 @@ Example response:
## Get single repository branch
-Get a single project repository branch. This endpoint can be accessed without
-authentication if the repository is publicly accessible.
+Get a single project repository branch.
-```
+NOTE: **Note:**
+This endpoint can be accessed without authentication if the repository is publicly accessible.
+
+```text
GET /projects/:id/repository/branches/:branch
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
+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. |
+| `branch` | string | yes | Name of the branch. |
-```bash
+Example request:
+
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/master
```
@@ -100,120 +116,34 @@ Example response:
## Protect repository branch
->**Note:** This API endpoint is deprecated in favor of `POST /projects/:id/protected_branches`.
-
-Protects a single project repository branch. This is an idempotent function,
-protecting an already protected repository branch still returns a `200 OK`
-status code.
-
-```
-PUT /projects/:id/repository/branches/:branch/protect
-```
-
-```bash
-curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true
-```
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
-| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
-| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
-
-Example response:
-
-```json
-{
- "commit": {
- "author_email": "john@example.com",
- "author_name": "John Smith",
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00",
- "committer_email": "john@example.com",
- "committer_name": "John Smith",
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "short_id": "7b5c3cc",
- "title": "add projects API",
- "message": "add projects API",
- "parent_ids": [
- "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- ]
- },
- "name": "master",
- "merged": false,
- "protected": true,
- "default": true,
- "developers_can_push": true,
- "developers_can_merge": true,
- "can_push": true
-}
-```
+See [`POST /projects/:id/protected_branches`](protected_branches.md#protect-repository-branches) for
+information on protecting repository branches.
## Unprotect repository branch
->**Note:** This API endpoint is deprecated in favor of `DELETE /projects/:id/protected_branches/:name`
+See [`DELETE /projects/:id/protected_branches/:name`](protected_branches.md#unprotect-repository-branches)
+for information on unprotecting repository branches.
-Unprotects a single project repository branch. This is an idempotent function,
-unprotecting an already unprotected repository branch still returns a `200 OK`
-status code.
-
-```
-PUT /projects/:id/repository/branches/:branch/unprotect
-```
-
-```bash
-curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/master/unprotect
-```
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
+## Create repository branch
-Example response:
+Create a new branch in the repository.
-```json
-{
- "commit": {
- "author_email": "john@example.com",
- "author_name": "John Smith",
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00",
- "committer_email": "john@example.com",
- "committer_name": "John Smith",
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "short_id": "7b5c3cc",
- "title": "add projects API",
- "message": "add projects API",
- "parent_ids": [
- "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- ]
- },
- "name": "master",
- "merged": false,
- "protected": false,
- "default": true,
- "developers_can_push": false,
- "developers_can_merge": false,
- "can_push": true
-}
+```text
+POST /projects/:id/repository/branches
```
-## Create repository branch
+Parameters:
-```
-POST /projects/:id/repository/branches
-```
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:-------------------------------------------------------------------------------------------------------------|
+| `id` | integer | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `branch` | string | yes | Name of the branch. |
+| `ref` | string | yes | Branch name or commit SHA to create branch from. |
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
-| `ref` | string | yes | The branch name or commit SHA to create branch from |
+Example request:
-```bash
-curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/branches?branch=newbranch&ref=master"
+```sh
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches?branch=newbranch&ref=master
```
Example response:
@@ -247,36 +177,47 @@ Example response:
## Delete repository branch
-```
+Delete a branch from the repository.
+
+NOTE: **Note:**
+In the case of an error, an explanation message is provided.
+
+```text
DELETE /projects/:id/repository/branches/:branch
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
+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. |
+| `branch` | string | yes | Name of the branch. |
-In case of an error, an explaining message is provided.
+Example request:
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/branches/newbranch"
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/newbranch
```
## Delete merged branches
Will delete all branches that are merged into the project's default branch.
-Protected branches will not be deleted as part of this operation.
+NOTE: **Note:**
+[Protected branches](../user/project/protected_branches.md) will not be deleted as part of this operation.
-```
+```text
DELETE /projects/:id/repository/merged_branches
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+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. |
+Example request:
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/merged_branches"
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/merged_branches
```
diff --git a/doc/api/features.md b/doc/api/features.md
index 59f1005ef72..47f104e1f20 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -60,9 +60,10 @@ POST /features/:name
| `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
| `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username |
+| `project` | string | no | A projects path, for example 'gitlab-org/gitlab-ce' |
-Note that you can enable or disable a feature for both a `feature_group` and a
-`user` with a single API call.
+Note that you can enable or disable a feature for a `feature_group`, a `user`,
+and a `project` in a single API call.
```bash
curl --data "value=30" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features/new_library
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 2d9114c40ea..907b443d355 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -357,12 +357,14 @@ Example response:
{
"group_id": 4,
"group_name": "Twitter",
+ "group_full_path": "twitter",
"group_access_level": 30,
"expires_at": null
},
{
"group_id": 3,
"group_name": "Gitlab Org",
+ "group_full_path": "gitlab-org",
"group_access_level": 10,
"expires_at": "2018-08-14"
}
diff --git a/doc/api/import.md b/doc/api/import.md
new file mode 100644
index 00000000000..9f8e0d232c6
--- /dev/null
+++ b/doc/api/import.md
@@ -0,0 +1,33 @@
+# Import API
+
+## Import repository from GitHub
+
+Import your projects from GitHub to GitLab via the API.
+
+```
+POST /import/github
+```
+
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| `personal_access_token` | string | yes | GitHub personal access token |
+| `repo_id` | integer | yes | GitHub repository ID |
+| `new_name` | string | no | New repo name |
+| `target_namespace` | string | yes | Namespace to import repo into |
+
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "personal_access_token=abc123&repo_id=12345&target_namespace=root" https://gitlab.example.com/api/v4/import/github
+```
+
+Example response:
+
+```json
+{
+ "id": 27,
+ "name": "my-repo",
+ "full_path": "/root/my-repo",
+ "full_name": "Administrator / my-repo"
+}
+```
+
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 538cd34de43..1296b435792 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -525,11 +525,13 @@ GET /projects/:id
{
"group_id": 4,
"group_name": "Twitter",
+ "group_full_path": "twitter",
"group_access_level": 30
},
{
"group_id": 3,
"group_name": "Gitlab Org",
+ "group_full_path": "gitlab-org",
"group_access_level": 10
}
],
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index fef367051bf..cd1a6695fbd 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -521,11 +521,11 @@ stages:
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
- CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project/my-image:$CI_COMMIT_REF_SLUG
- CONTAINER_RELEASE_IMAGE: registry.example.com/my-group/my-project/my-image:latest
+ CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+ CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
build:
stage: build
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index d4f0da52e53..d7ab6fc506d 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -78,118 +78,6 @@ A job is defined by a list of parameters that define the job behavior.
| [retry](#retry) | no | Define when and how many times a job can be auto-retried in case of a failure |
| [parallel](#parallel) | no | Defines how many instances of a job should be run in parallel |
-### `extends`
-
-> Introduced in GitLab 11.3.
-
-`extends` defines an entry name that a job that uses `extends` is going to
-inherit from.
-
-It is an alternative to using [YAML anchors](#anchors) and is a little
-more flexible and readable:
-
-```yaml
-.tests:
- script: rake test
- stage: test
- only:
- refs:
- - branches
-
-rspec:
- extends: .tests
- script: rake rspec
- only:
- variables:
- - $RSPEC
-```
-
-In the example above, the `rspec` job inherits from the `.tests` template job.
-GitLab will perform a reverse deep merge based on the keys. GitLab will:
-
-- Merge the `rspec` contents into `.tests` recursively.
-- Not merge the values of the keys.
-
-This results in the following `rspec` job:
-
-```yaml
-rspec:
- script: rake rspec
- stage: test
- only:
- refs:
- - branches
- variables:
- - $RSPEC
-```
-
-NOTE: **Note:**
-Note that `script: rake test` has been overwritten by `script: rake rspec`.
-
-If you do want to include the `rake test`, have a look at [before_script-and-after_script](#before_script-and-after_script).
-
-`.tests` in this example is a [hidden key](#hidden-keys-jobs), but it's
-possible to inherit from regular jobs as well.
-
-`extends` supports multi-level inheritance, however it is not recommended to
-use more than three levels. The maximum nesting level that is supported is 10.
-The following example has two levels of inheritance:
-
-```yaml
-.tests:
- only:
- - pushes
-
-.rspec:
- extends: .tests
- script: rake rspec
-
-rspec 1:
- variables:
- RSPEC_SUITE: '1'
- extends: .rspec
-
-rspec 2:
- variables:
- RSPEC_SUITE: '2'
- extends: .rspec
-
-spinach:
- extends: .tests
- script: rake spinach
-```
-
-`extends` works across configuration files combined with [`include`](#include).
-
-### `pages`
-
-`pages` is a special job that is used to upload static content to GitLab that
-can be used to serve your website. It has a special syntax, so the two
-requirements below must be met:
-
-1. Any static content must be placed under a `public/` directory
-1. `artifacts` with a path to the `public/` directory must be defined
-
-The example below simply moves all files from the root of the project to the
-`public/` directory. The `.public` workaround is so `cp` doesn't also copy
-`public/` to itself in an infinite loop:
-
-```yaml
-pages:
- stage: deploy
- script:
- - mkdir .public
- - cp -r * .public
- - mv .public public
- artifacts:
- paths:
- - public
- only:
- - master
-```
-
-Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
-
## `image` and `services`
This allows to specify a custom Docker image and a list of services that can be
@@ -260,7 +148,7 @@ There are also two edge cases worth mentioning:
1. If no `stages` are defined in `.gitlab-ci.yml`, then the `build`,
`test` and `deploy` are allowed to be used as job's stage by default.
-2. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
+1. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
## `stage`
@@ -328,7 +216,7 @@ a "key: value" pair. Be careful when using special characters:
jobs are created:
1. `only` defines the names of branches and tags for which the job will run.
-2. `except` defines the names of branches and tags for which the job will
+1. `except` defines the names of branches and tags for which the job will
**not** run.
There are a few rules that apply to the usage of job policy:
@@ -677,9 +565,9 @@ cleanup_job:
The above script will:
1. Execute `cleanup_build_job` only when `build_job` fails.
-2. Always execute `cleanup_job` as the last step in pipeline regardless of
+1. Always execute `cleanup_job` as the last step in pipeline regardless of
success or failure.
-3. Allow you to manually execute `deploy_job` from GitLab's UI.
+1. Allow you to manually execute `deploy_job` from GitLab's UI.
### `when:manual`
@@ -1622,7 +1510,6 @@ Possible values for `when` are:
- `missing_dependency_failure`: Retry if a dependency was missing.
- `runner_unsupported`: Retry if the runner was unsupported.
-
## `parallel`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22631) in GitLab 11.5.
@@ -1645,193 +1532,213 @@ test:
## `include`
-> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
-> Available for Starter, Premium and Ultimate since 10.6.
-> Behaviour expanded in GitLab 10.8 to allow more flexible overriding.
-> [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603)
-to GitLab Core in 11.4
-> In GitLab 11.7, support for [including GitLab-supplied templates directly](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445) and support for [including templates from another repository](https://gitlab.com/gitlab-org/gitlab-ce/issues/53903) was added.
+> - Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
+> - Available for Starter, Premium and Ultimate since 10.6.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603) to GitLab Core in 11.4.
Using the `include` keyword, you can allow the inclusion of external YAML files.
+`include` requires the external YAML file to have the extensions `.yml` or `.yaml`,
+otherwise the external file will not be included.
-In the following example, the content of `.before-script-template.yml` will be
-automatically fetched and evaluated along with the content of `.gitlab-ci.yml`:
+The files defined in `include` are:
-```yaml
-# Content of https://gitlab.com/awesome-project/raw/master/.before-script-template.yml
+- Deep merged with those in `.gitlab-ci.yml`.
+- Always evaluated first and merged with the content of `.gitlab-ci.yml`,
+ regardless of the position of the `include` keyword.
-before_script:
- - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
- - gem install bundler --no-document
- - bundle install --jobs $(nproc) "${FLAGS[@]}"
-```
+TIP: **Tip:**
+Use merging to customize and override included CI/CD configurations with local
+definitions.
-```yaml
-# Content of .gitlab-ci.yml
+Recursive includes are not supported. Your external files should not use the
+`include` keyword as it will be ignored.
-include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+NOTE: **Note:**
+Using YAML aliases across different YAML files sourced by `include` is not
+supported. You must only refer to aliases in the same file. Instead
+of using YAML anchors, you can use the [`extends` keyword](#extends).
-rspec:
- script:
- - bundle exec rspec
-```
+`include` supports four include methods:
-NOTE: **Note:**
-`include` requires the external YAML files to have the extensions `.yml` or `.yaml`.
-The external file will not be included if the extension is missing.
+- [`local`](#includelocal)
+- [`file`](#includefile)
+- [`template`](#includetemplate)
+- [`remote`](#includeremote)
-You can include your extra YAML file either as a single string or
-as an array of multiple values. You can also use full paths or
-relative URLs. The following examples are both valid:
+See [usage examples](#include-examples).
-```yaml
-# Single string
+### `include:local`
-include: '/templates/.after-script-template.yml'
-```
+`include:local` includes a file from the same repository as `.gitlab-ci.yml`.
+It's referenced using full paths relative to the root directory (`/`).
-```yaml
-# Single string
+You can only use files that are currently tracked by Git on the same branch
+your configuration file is on. In other words, when using a `include:local`, make
+sure that both `.gitlab-ci.yml` and the local file are on the same branch.
+
+NOTE: **Note:**
+Including local files through Git submodules paths is not supported.
+Example:
+
+```yaml
include:
- file: '/templates/.after-script-template.yml'
+ - local: '/templates/.gitlab-ci-template.yml'
```
-```yaml
-# Array
+### `include:file`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53903) in GitLab 11.7.
+
+To include files from another private project under the same GitLab instance,
+use `include:file`. This file is referenced using full paths relative to the
+root directory (`/`). For example:
+```yaml
include:
- - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- - '/templates/.after-script-template.yml'
+ - project: 'my-group/my-project'
+ file: '/templates/.gitlab-ci-template.yml'
```
-```yaml
-# Array mixed syntax
+You can also specify `ref`, with the default being the `HEAD` of the project:
+```yaml
include:
- - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- - '/templates/.after-script-template.yml'
- - template: Auto-DevOps.gitlab-ci.yml
+ - project: 'my-group/my-project'
+ ref: master
+ file: '/templates/.gitlab-ci-template.yml'
+
+ - project: 'my-group/my-project'
+ ref: v1.0.0
+ file: '/templates/.gitlab-ci-template.yml'
+
+ - project: 'my-group/my-project'
+ ref: 787123b47f14b552955ca2786bc9542ae66fee5b # Git SHA
+ file: '/templates/.gitlab-ci-template.yml'
```
-```yaml
-# Array
+### `include:template`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445) in GitLab 11.7.
+`include:template` can be used to include `.gitlab-ci.yml` templates that are
+[shipped with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates).
+
+For example:
+
+```yaml
+# File sourced from GitLab's template collection
include:
- - remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- - local: '/templates/.after-script-template.yml'
- template: Auto-DevOps.gitlab-ci.yml
```
----
+### `include:remote`
+
+`include:remote` can be used to include a file from a different location,
+using HTTP/HTTPS, referenced by using the full URL. The remote file must be
+publicly accessible through a simple GET request as authentication schemas
+in the remote URL is not supported. For example:
-`include` supports four types of files:
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
+```
-- **local** to the same repository, referenced by using full paths in the same
- repository, with `/` being the root directory. For example:
+NOTE: **Note for GitLab admins:**
+In order to include files from another repository inside your local network,
+you may need to enable the **Allow requests to the local network from hooks and services** checkbox
+located in the **Admin area > Settings > Network > Outbound requests** section.
- ```yaml
- # Within the repository
- include: '/templates/.gitlab-ci-template.yml'
- ```
+### `include` examples
- Or using:
+Here are a few more `include` examples.
- ```yaml
- # Within the repository
- include:
- local: '/templates/.gitlab-ci-template.yml'
- ```
+#### Single string or array of multiple values
- NOTE: **Note:**
- You can only use files that are currently tracked by Git on the same branch
- your configuration file is. In other words, when using a **local file**, make
- sure that both `.gitlab-ci.yml` and the local file are on the same branch.
+You can include your extra YAML file(s) either as a single string or
+an array of multiple values. The following examples are all valid.
- NOTE: **Note:**
- We don't support the inclusion of local files through Git submodules paths.
+Single string with the `include:local` method implied:
-- **file** from another repository, referenced by using full paths in the same
- repository, with `/` being the root directory. For example:
+```yaml
+include: '/templates/.after-script-template.yml'
+```
- ```yaml
- include:
- project: 'my-group/my-project'
- file: '/templates/.gitlab-ci-template.yml'
- ```
+Array with `include` method implied:
- You can also specify `ref:`. The default `ref:` is the `HEAD` of the project:
+```yaml
+include:
+ - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - '/templates/.after-script-template.yml'
+```
- ```yaml
- include:
- - project: 'my-group/my-project'
- ref: master
- file: '/templates/.gitlab-ci-template.yml'
+Single string with `include` method specified explicitly:
- - project: 'my-group/my-project'
- ref: v1.0.0
- file: '/templates/.gitlab-ci-template.yml'
+```yaml
+include:
+ remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+```
- - project: 'my-group/my-project'
- ref: 787123b47f14b552955ca2786bc9542ae66fee5b # git sha
- file: '/templates/.gitlab-ci-template.yml'
- ```
+Array with `include:remote` being the single item:
-- **remote** in a different location, accessed using HTTP/HTTPS, referenced
- using the full URL. For example:
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+```
- ```yaml
- # File sourced from outside repository
- include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
- ```
+Array with multiple `include` methods specified explicitly:
- Or using:
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - local: '/templates/.after-script-template.yml'
+ - template: Auto-DevOps.gitlab-ci.yml
+```
- ```yaml
- # File sourced from outside repository
- include:
- remote: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
- ```
+Array mixed syntax:
- NOTE: **Note:**
- The remote file must be publicly accessible through a simple GET request, as we don't support authentication schemas in the remote URL.
+```yaml
+include:
+ - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - '/templates/.after-script-template.yml'
+ - template: Auto-DevOps.gitlab-ci.yml
+ - project: 'my-group/my-project'
+ ref: master
+ file: '/templates/.gitlab-ci-template.yml'
+```
- NOTE: **Note:**
- In order to include files from another repository inside your local network,
- you may need to enable the **Allow requests to the local network from hooks and services** checkbox
- located in the **Settings > Network > Outbound requests** section within the **Admin area**.
+#### Re-using a `before_script` template
-- **template** included with GitLab. For example:
+In the following example, the content of `.before-script-template.yml` will be
+automatically fetched and evaluated along with the content of `.gitlab-ci.yml`.
- ```yaml
- # File sourced from GitLab's template collection
- include:
- template: Auto-DevOps.gitlab-ci.yml
- ```
+Content of `https://gitlab.com/awesome-project/raw/master/.before-script-template.yml`:
- NOTE: **Note:**
- Templates included this way are sourced from [lib/gitlab/ci/templates](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates).
+```yaml
+before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - gem install bundler --no-document
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+```
----
+Content of `.gitlab-ci.yml`:
+```yaml
+include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
-Since GitLab 10.8 we are now deep merging the files defined in `include`
-with those in `.gitlab-ci.yml`. Files defined by `include` are always
-evaluated first and merged with the content of `.gitlab-ci.yml`, no
-matter the position of the `include` keyword. You can take advantage of
-merging to customize and override details in included CI
-configurations with local definitions.
+rspec:
+ script:
+ - bundle exec rspec
+```
-NOTE: **Note:**
-The recursive includes are not supported, meaning your external files
-should not use the `include` keyword, as it will be ignored.
+#### Overriding external template values
The following example shows specific YAML-defined variables and details of the
`production` job from an include file being customized in `.gitlab-ci.yml`.
-```yaml
-# Content of https://company.com/autodevops-template.yml
+Content of `https://company.com/autodevops-template.yml`:
+```yaml
variables:
POSTGRES_USER: user
POSTGRES_PASSWORD: testing_password
@@ -1849,9 +1756,9 @@ production:
- master
```
-```yaml
-# Content of .gitlab-ci.yml
+Content of `.gitlab-ci.yml`:
+```yaml
include: 'https://company.com/autodevops-template.yml'
image: alpine:latest
@@ -1878,11 +1785,11 @@ with the environment url of the `production` job defined in
The merging lets you extend and override dictionary mappings, but
you cannot add or modify items to an included array. For example, to add
an additional item to the production job script, you must repeat the
-existing script items.
+existing script items:
-```yaml
-# Content of https://company.com/autodevops-template.yml
+Content of `https://company.com/autodevops-template.yml`:
+```yaml
production:
stage: production
script:
@@ -1890,9 +1797,9 @@ production:
- deploy
```
-```yaml
-# Content of .gitlab-ci.yml
+Content of `.gitlab-ci.yml`:
+```yaml
include: 'https://company.com/autodevops-template.yml'
stages:
@@ -1909,10 +1816,140 @@ In this case, if `install_dependencies` and `deploy` were not repeated in
`.gitlab-ci.yml`, they would not be part of the script for the `production`
job in the combined CI configuration.
+## `extends`
+
+> Introduced in GitLab 11.3.
+
+`extends` defines an entry name that a job that uses `extends` is going to
+inherit from.
+
+It is an alternative to using [YAML anchors](#anchors) and is a little
+more flexible and readable:
+
+```yaml
+.tests:
+ script: rake test
+ stage: test
+ only:
+ refs:
+ - branches
+
+rspec:
+ extends: .tests
+ script: rake rspec
+ only:
+ variables:
+ - $RSPEC
+```
+
+In the example above, the `rspec` job inherits from the `.tests` template job.
+GitLab will perform a reverse deep merge based on the keys. GitLab will:
+
+- Merge the `rspec` contents into `.tests` recursively.
+- Not merge the values of the keys.
+
+This results in the following `rspec` job:
+
+```yaml
+rspec:
+ script: rake rspec
+ stage: test
+ only:
+ refs:
+ - branches
+ variables:
+ - $RSPEC
+```
+
NOTE: **Note:**
-We currently do not support using YAML aliases across different YAML files
-sourced by `include`. You must only refer to aliases in the same file. Instead
-of using YAML anchors you can use [`extends` keyword](#extends).
+Note that `script: rake test` has been overwritten by `script: rake rspec`.
+
+If you do want to include the `rake test`, see [`before_script` and `after_script`](#before_script-and-after_script).
+
+`.tests` in this example is a [hidden key](#hidden-keys-jobs), but it's
+possible to inherit from regular jobs as well.
+
+`extends` supports multi-level inheritance, however it is not recommended to
+use more than three levels. The maximum nesting level that is supported is 10.
+The following example has two levels of inheritance:
+
+```yaml
+.tests:
+ only:
+ - pushes
+
+.rspec:
+ extends: .tests
+ script: rake rspec
+
+rspec 1:
+ variables:
+ RSPEC_SUITE: '1'
+ extends: .rspec
+
+rspec 2:
+ variables:
+ RSPEC_SUITE: '2'
+ extends: .rspec
+
+spinach:
+ extends: .tests
+ script: rake spinach
+```
+
+## Using `extends` and `include` together
+
+`extends` works across configuration files combined with `include`.
+
+For example, if you have a local `included.yml` file:
+
+```yaml
+.template:
+ script:
+ - echo Hello!
+```
+
+Then, in `.gitlab-ci.yml` you can use it like this:
+
+```yaml
+include: included.yml
+
+useTemplate:
+ image: alpine
+ extends: .template
+```
+
+This will run a job called `useTemplate` that runs `echo Hello!` as defined in
+the `.template` job, and uses the `alpine` Docker image as defined in the local job.
+
+## `pages`
+
+`pages` is a special job that is used to upload static content to GitLab that
+can be used to serve your website. It has a special syntax, so the two
+requirements below must be met:
+
+- Any static content must be placed under a `public/` directory.
+- `artifacts` with a path to the `public/` directory must be defined.
+
+The example below simply moves all files from the root of the project to the
+`public/` directory. The `.public` workaround is so `cp` doesn't also copy
+`public/` to itself in an infinite loop:
+
+```yaml
+pages:
+ stage: deploy
+ script:
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+```
+
+Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
## `variables`
@@ -1951,9 +1988,9 @@ which can be set in GitLab's UI.
### Git strategy
-> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
- completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
- v1.7+.
+> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
+> completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
+> v1.7+.
You can set the `GIT_STRATEGY` used for getting recent application code, either
globally or per-job in the [`variables`](#variables) section. If left
@@ -2283,8 +2320,9 @@ capitalization, the commit will be created but the pipeline will be skipped.
Alternatively, one can pass the `ci.skip` [Git push option][push-option] if
using Git 2.10 or newer:
-```
-$ git push -o ci.skip
+
+```sh
+git push -o ci.skip
```
## Validate the .gitlab-ci.yml
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 7c7da50a149..6d9149004fe 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -6,7 +6,7 @@ scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
-- Type: ~"feature proposal", ~bug, ~customer, etc.
+- Type: ~feature, ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~Plan, ~Manage, ~Quality, etc.
- Stage: ~"devops:plan", ~"devops:create", etc.
@@ -27,8 +27,8 @@ labels, you can _always_ add the team and type, and often also the subject.
Type labels are very important. They define what kind of issue this is. Every
issue should have one or more.
-Examples of type labels are ~"feature proposal", ~bug, ~customer, ~security,
-and ~"direction".
+Examples of type labels are ~feature, ~bug, ~customer, ~security,
+and ~direction.
A number of type labels have a priority assigned to them, which automatically
makes them float to the top, depending on their importance.
@@ -200,7 +200,7 @@ We add the ~"Accepting merge requests" label to:
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
solve in the ~"Next Patch Release")
-- Small ~"feature proposal"
+- Small ~feature
- Small ~"technical debt" issues
After adding the ~"Accepting merge requests" label, we try to estimate the
@@ -259,10 +259,10 @@ For feature proposals for EE, open an issue on the
[issue tracker of EE][ee-tracker].
In order to help track the feature proposals, we have created a
-[`feature proposal`][fpl] label. For the time being, users that are not members
+[`feature`][fl] label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team]
-members to add the label ~"feature proposal" to the issue or add the following
-code snippet right after your description in a new line: `~"feature proposal"`.
+members to add the label ~feature to the issue or add the following
+code snippet right after your description in a new line: `~feature`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
@@ -276,7 +276,7 @@ need to ask one of the [core team] members to add the label, if you do not have
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
-[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
+[fl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature
## Issue tracker guidelines
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 092bbdac037..c188819560e 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -389,7 +389,7 @@ Which renders to:
> ### This is an `h3`
>{:.no_toc}
-## Specific sections and terms
+## Terms
To maintain consistency through GitLab documentation, the following guides documentation authors
on agreed styles and usage of terms.
@@ -418,7 +418,7 @@ The following are recommended verbs for specific uses.
|:------------|:--------------------------------|:-------------------|
| "go" | making a browser go to location | "navigate", "open" |
-### GitLab versions and tiers
+## GitLab versions and tiers
- Every piece of documentation that comes with a new feature should declare the
GitLab version that feature got introduced. Right below the heading add a
@@ -443,7 +443,7 @@ The following are recommended verbs for specific uses.
> [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
```
-#### Early versions of EE
+### Early versions of EE
If the feature was created before GitLab 9.2 (before [different EE tiers were introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1851)):
@@ -456,7 +456,7 @@ For example:
> [Introduced](<link-to-issue>) in GitLab Enterprise Edition 9.0. Available in [GitLab Premium](https://about.gitlab.com/pricing/).
```
-### Product badges
+## Product badges
When a feature is available in EE-only tiers, add the corresponding tier according to the
feature availability:
@@ -477,12 +477,16 @@ keyword "only":
The tier should be ideally added to headers, so that the full badge will be displayed.
However, it can be also mentioned from paragraphs, list items, and table cells. For these cases,
the tier mention will be represented by an orange question mark that will show the tiers on hover.
-E.g., `**[STARTER]**` renders **[STARTER]**, `**[STARTER ONLY]**` renders **[STARTER ONLY]**.
+
+For example:
+
+- `**[STARTER]**` renders as **[STARTER]**
+- `**[STARTER ONLY]**` renders as **[STARTER ONLY]**
The absence of tiers' mentions mean that the feature is available in GitLab Core,
GitLab.com Free, and all higher tiers.
-#### How it works
+### How it works
Introduced by [!244](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/244),
the special markup `**[STARTER]**` will generate a `span` element to trigger the
diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md
index bbb2313ea7b..f6bc9fb0979 100644
--- a/doc/development/testing_guide/flaky_tests.md
+++ b/doc/development/testing_guide/flaky_tests.md
@@ -5,6 +5,18 @@
It's a test that sometimes fails, but if you retry it enough times, it passes,
eventually.
+## Quarantined tests
+
+Tests can be put in quarantine by assigning `:quarantine` metadata. This means
+they will be skipped unless run with `--tag quarantine`. This can be used for
+tests that are expected to fail while a fix is in progress (similar to how
+[`skip` or `pending`](https://relishapp.com/rspec/rspec-core/v/3-8/docs/pending-and-skipped-examples)
+ can be used).
+
+```
+bin/rspec --tag quarantine
+```
+
## Automatic retries and flaky tests detection
On our CI, we use [rspec-retry] to automatically retry a failing example a few
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 309babb5f94..13f178297b9 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -74,27 +74,27 @@ Note that both jobs first wait for the `review-deploy` job to be finished.
### Run a Rails console?
1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps)
- , e.g. `review-29951-issu-id2qax`.
-1. Find and open the `task-runner` Deployment, e.g. `review-29951-issu-id2qax-task-runner`.
-1. Click on the Pod in the "Managed pods" section, e.g. `review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz`.
+ , e.g. `review-mr-24388-314997`.
+1. Find and open the `task-runner` Deployment, e.g. `review-mr-24388-314997-task-runner`.
+1. Click on the Pod in the "Managed pods" section, e.g. `review-mr-24388-314997-task-runner-d5455cc8-2lsvz`.
1. Click on the `KUBECTL` dropdown, then `Exec` -> `task-runner`.
1. Replace `-c task-runner -- ls` with `-- /srv/gitlab/bin/rails c` from the
default command or
- - Run `kubectl exec --namespace review-apps-ce -it review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz -- /srv/gitlab/bin/rails c`
+ - Run `kubectl exec --namespace review-apps-ce -it review-mr-24388-314997-task-runner-d5455cc8-2lsvz -- /srv/gitlab/bin/rails c`
and
- Replace `review-apps-ce` with `review-apps-ee` if the Review App
is running EE, and
- - Replace `review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz`
+ - Replace `review-mr-24388-314997-task-runner-d5455cc8-2lsvz`
with your Pod's name.
### Dig into a Pod's logs?
1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps)
- , e.g. `review-1979-1-mul-dnvlhv`.
+ , e.g. `review-mr-24388-314997`.
1. Find and open the `migrations` Deployment, e.g.
- `review-1979-1-mul-dnvlhv-migrations.1`.
+ `review-mr-24388-314997-migrations.1`.
1. Click on the Pod in the "Managed pods" section, e.g.
- `review-1979-1-mul-dnvlhv-migrations.1-nqwtx`.
+ `review-mr-24388-314997-migrations.1-nqwtx`.
1. Click on the `Container logs` link.
## Frequently Asked Questions
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 678c306a484..2eba2cc4847 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -117,6 +117,13 @@ sudo make prefix=/usr/local install
# When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git
```
+For the [Custom Favicon](../customization/favicon.md) to work, graphicsmagick
+needs to be installed.
+
+```sh
+sudo apt-get install -y graphicsmagick
+```
+
**Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://gitlab.com/gitlab-org/gitlab-ce/issues/12754) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with:
```sh
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 780e9b8783e..68e50a61151 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -683,6 +683,8 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1. |
| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html); defaults to 1 |
| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
+| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
+| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
diff --git a/doc/user/award_emojis.md b/doc/user/award_emojis.md
index 93be3da44d4..e4fd08a582c 100644
--- a/doc/user/award_emojis.md
+++ b/doc/user/award_emojis.md
@@ -1,24 +1,24 @@
# Award emoji
-> **Notes:**
-> - First [introduced][1825] in GitLab 8.2.
-> - GitLab 9.0 [introduced][ce-9570] the usage of native emojis if the platform
+> - First [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825) in GitLab 8.2.
+> - GitLab 9.0 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9570) the usage of native emoji if the platform
> supports them and falls back to images or CSS sprites. This change greatly
-> improved the award emoji performance overall.
+> improved award emoji performance overall.
When you're collaborating online, you get fewer opportunities for high-fives
-and thumbs-ups. Emoji can be awarded to issues, merge requests, snippets, and
-virtually everywhere where you can have a discussion.
+and thumbs-ups. Emoji can be awarded to [issues](project/issues/index.md), [merge requests](project/merge_requests/index.md),
+[snippets](snippets.md), and anywhere you can have a discussion.
![Award emoji](img/award_emoji_select.png)
Award emoji make it much easier to give and receive feedback without a long
-comment thread. Comments that are only emoji will automatically become
-award emoji.
+comment thread.
+
+For information on the relevant API, see [Award Emoji API](../api/award_emoji.md).
## Sort issues and merge requests on vote count
-> [Introduced][2871] in GitLab 8.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781) in GitLab 8.5.
You can quickly sort issues and merge requests by the number of votes they
have received. The sort options can be found in the dropdown menu as "Most
@@ -32,20 +32,16 @@ downvotes.
## Award emoji for comments
-> [Introduced][4291] in GitLab 8.9.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291) in GitLab 8.9.
Award emoji can also be applied to individual comments when you want to
celebrate an accomplishment or agree with an opinion.
-To add an award emoji, click the smile in the top right of the comment and pick
-an emoji from the dropdown. If you want to remove an award emoji, just click
-the emoji again and the vote will be removed.
+To:
+
+- Add an award emoji, click the smile in the top right of the comment and pick an emoji from the dropdown.
+- Remove an award emoji, click the emoji again and the vote will be removed.
![Picking an emoji for a comment](img/award_emoji_comment_picker.png)
![An award emoji has been applied to a comment](img/award_emoji_comment_awarded.png)
-
-[2871]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781
-[1825]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825
-[4291]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291
-[ce-9570]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9570
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 9379d047fca..84f4b0b3922 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -280,7 +280,7 @@ Additionally locked issues can not be reopened.
For issues with many comments like activity notes and user comments, sometimes
finding useful information can be hard. There is a way to filter comments from single notes and discussions for merge requests and issues.
-From a merge request's **Discussion** tab, or from an issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options:
+From a merge request's **Discussion** tab, or from an epic/issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options:
- **Show all activity**: displays all user comments and system notes
(issue updates, mentions from other issues, changes to the description, etc).
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 4d56b33f684..b6f8f55978b 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -164,9 +164,11 @@ and you can choose the group of people to be notified.
Here's a list of what you can't do with subgroups:
-- [GitLab Pages](../../project/pages/index.md) are not currently working for
- projects hosted under a subgroup. That means that only projects hosted under
- the first parent group will work.
+- [GitLab Pages](../../project/pages/index.md) supports projects hosted under
+ a subgroup, but not subgroup websites.
+ That means that only the highest-level group supports
+ [group websites](../../project/pages/introduction.html#user-or-group-pages),
+ although you can have project websites under a subgroup.
- It is not possible to share a project with a group that's an ancestor of
the group the project is in. That means you can only share as you walk down
the hierarchy. For example, `group/subgroup01/project` **cannot** be shared
diff --git a/doc/user/project/integrations/prometheus_library/index.md b/doc/user/project/integrations/prometheus_library/index.md
index 9b9b4f6c8ca..a79bc2bce06 100644
--- a/doc/user/project/integrations/prometheus_library/index.md
+++ b/doc/user/project/integrations/prometheus_library/index.md
@@ -10,7 +10,8 @@ Currently supported exporters are:
- [Kubernetes](kubernetes.md)
- [NGINX](nginx.md)
-- [NGINX Ingress Controller](nginx_ingress.md)
+- [NGINX Ingress Controller 0.9.0-0.15.x](nginx_ingress_vts.md)
+- [NGINX Ingress Controller 0.16.0+](nginx_ingress.md)
- [HAProxy](haproxy.md)
- [Amazon Cloud Watch](cloudwatch.md)
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index d5f77d622be..b7601f26802 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -1,8 +1,10 @@
# Monitoring NGINX Ingress Controller
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22133) in GitLab 11.7.
-GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built in Prometheus metrics included in [version 0.9.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#09-beta1) and above of the ingress.
+NOTE: **Note:** NGINX Ingress versions prior to 0.16.0 offer an included [VTS Prometheus metrics exporter](nginx_ingress_vts.md), which exports metrics different than the built-in metrics.
+
+GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built-in Prometheus metrics included starting with [version 0.16.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0160).
## Requirements
@@ -12,9 +14,9 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
| Name | Query |
| ---- | ----- |
-| Throughput (req/sec) | sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code) |
-| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
-| HTTP Error Rate (%) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100 |
+| Throughput (req/sec) | sum(label_replace(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m]), "status_code", "${1}xx", "status", "(.)..")) by (status_code) |
+| Latency (ms) | sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000 |
+| HTTP Error Rate (%) | sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100 |
## Configuring NGINX ingress monitoring
@@ -22,9 +24,9 @@ If you have deployed NGINX Ingress using GitLab's [Kubernetes cluster integratio
For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
-- NGINX Ingress should be version 0.9.0 or above, with metrics enabled
-- NGINX Ingress should be annotated for Prometheus monitoring
-- Prometheus should be configured to monitor annotated pods
+- NGINX Ingress should be version 0.16.0 or above, with metrics enabled.
+- NGINX Ingress should be annotated for Prometheus monitoring.
+- Prometheus should be configured to monitor annotated pods.
### About managed NGINX Ingress deployments
@@ -32,9 +34,9 @@ NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [o
NGINX is configured for Prometheus monitoring, by setting:
-- `enable-vts-status: "true"`, to export Prometheus metrics
-- `prometheus.io/scrape: "true"`, to enable automatic discovery
-- `prometheus.io/port: "10254"`, to specify the metrics port
+- `enable-vts-status: "true"`, to export Prometheus metrics.
+- `prometheus.io/scrape: "true"`, to enable automatic discovery.
+- `prometheus.io/port: "10254"`, to specify the metrics port.
When used in conjunction with the GitLab deployed Prometheus service, response metrics will be automatically collected.
@@ -51,6 +53,6 @@ Managing these settings depends on how NGINX ingress has been deployed. If you h
## Specifying the Environment label
-In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
+In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `ingress` label must `<CI_ENVIRONMENT_SLUG>`.
If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
new file mode 100644
index 00000000000..081eb8732ad
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
@@ -0,0 +1,58 @@
+# Monitoring NGINX Ingress Controller with VTS metrics
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5.
+
+NOTE: **Note:** [NGINX Ingress version 0.16](nginx_ingress.md) and above have built-in Prometheus metrics, which are different than the VTS based metrics.
+
+GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the included VTS Prometheus metrics exporter in [version 0.9.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#09-beta1) through [0.15.x](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0150).
+
+## Requirements
+
+[Prometheus integration](../prometheus.md) must be active.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code) |
+| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
+| HTTP Error Rate (%) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100 |
+
+## Configuring NGINX ingress monitoring
+
+If you have deployed NGINX Ingress using GitLab's [Kubernetes cluster integration](../../clusters/index.md#installing-applications), it will [automatically be monitored](#about-managed-nginx-ingress-deployments) by Prometheus.
+
+For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
+
+- NGINX Ingress should be version 0.9.0 or above, with metrics enabled.
+- NGINX Ingress should be annotated for Prometheus monitoring.
+- Prometheus should be configured to monitor annotated pods.
+
+### About managed NGINX Ingress deployments
+
+NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address).
+
+NGINX is configured for Prometheus monitoring, by setting:
+
+- `enable-vts-status: "true"`, to export Prometheus metrics.
+- `prometheus.io/scrape: "true"`, to enable automatic discovery.
+- `prometheus.io/port: "10254"`, to specify the metrics port.
+
+When used in conjunction with the GitLab deployed Prometheus service, response metrics will be automatically collected.
+
+### Manually setting up NGINX Ingress for Prometheus monitoring
+
+Version 0.9.0 and above of [NGINX ingress](https://github.com/kubernetes/ingress-nginx) have built-in support for exporting Prometheus metrics. To enable, a ConfigMap setting must be passed: `enable-vts-status: "true"`. Once enabled, a Prometheus metrics endpoint will start running on port 10254.
+
+Next, the ingress needs to be annotated for Prometheus monitoring. Two new annotations need to be added:
+
+- `prometheus.io/scrape: "true"`
+- `prometheus.io/port: "10254"`
+
+Managing these settings depends on how NGINX ingress has been deployed. If you have deployed via the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress), metrics can be enabled with `controller.stats.enabled` along with the required annotations. Alternatively it is possible edit the NGINX ingress YML directly in the [Kubernetes dashboard](https://github.com/kubernetes/dashboard).
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
+
+If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md
index 859ac92ef89..da6e6b5fd3a 100644
--- a/doc/user/project/merge_requests/allow_collaboration.md
+++ b/doc/user/project/merge_requests/allow_collaboration.md
@@ -1,20 +1,72 @@
# Allow collaboration on merge requests across forks
-> [Introduced][ce-17395] in GitLab 10.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395)
+ in GitLab 10.6.
+
+When a user opens a merge request from a fork, they are given the option to allow
+upstream members to collaborate with them on the source branch. This allows
+the members of the upstream project to make small fixes or rebase branches
+before merging, reducing the back and forth of accepting external contributions.
This feature is available for merge requests across forked projects that are
-publicly accessible. It makes it easier for members of projects to
-collaborate on merge requests across forks.
+publicly accessible.
When enabled for a merge request, members with merge access to the target
branch of the project will be granted write permissions to the source branch
of the merge request.
+## Enabling commit edits from upstream members
+
The feature can only be enabled by users who already have push access to the
-source project, and only lasts while the merge request is open.
+source project and only lasts while the merge request is open. Once enabled,
+upstream members will also be able to retry the pipelines and jobs of the
+merge request:
+
+1. Enable the contribution while creating or editing a merge request.
+
+ ![Enable contribution](img/allow_collaboration.png)
+
+1. Once the merge request is created, you'll see that commits from members who
+ can merge to the target branch are allowed.
+
+ ![Check that contribution is enabled](img/allow_collaboration_after_save.png)
+
+## Pushing to the fork as the upstream member
+
+If the creator of the merge request has enabled contributions from upstream
+members, you can push directly to the branch of the forked repository.
+
+Assuming that:
+
+- The forked project URL is `git@gitlab.com:thedude/awesome-project.git`.
+- The branch of the merge request is `update-docs`.
+
+Here's how the process would look like:
+
+1. First, you need to get the changes that the merge request has introduced.
+ Click the **Check out branch** button that has some pre-populated
+ commands that you can run.
+
+ ![Check out branch button](img/checkout_button.png)
+
+1. Use the copy to clipboard button to copy the first command and paste them
+ in your terminal:
+
+ ```sh
+ git fetch git@gitlab.com:thedude/awesome-project.git update-docs
+ git checkout -b thedude-awesome-project-update-docs FETCH_HEAD
+ ```
+
+ This will fetch the branch of the forked project and then create a local branch
+ based off the fetched branch.
-Enable this functionality while creating or editing a merge request:
+1. Make any changes you want and commit.
+1. Push to the forked project:
-![Enable collaboration](./img/allow_collaboration.png)
+ ```sh
+ git push git@gitlab.com:thedude/awesome-project.git thedude-awesome-project-update-docs:update-docs
+ ```
-[ce-17395]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395
+ 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.
diff --git a/doc/user/project/merge_requests/img/allow_collaboration.png b/doc/user/project/merge_requests/img/allow_collaboration.png
index 3c81e4c27b8..e40e8a6b11c 100644
--- a/doc/user/project/merge_requests/img/allow_collaboration.png
+++ b/doc/user/project/merge_requests/img/allow_collaboration.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/allow_collaboration_after_save.png b/doc/user/project/merge_requests/img/allow_collaboration_after_save.png
new file mode 100644
index 00000000000..4ba4c84c8c5
--- /dev/null
+++ b/doc/user/project/merge_requests/img/allow_collaboration_after_save.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/checkout_button.png b/doc/user/project/merge_requests/img/checkout_button.png
new file mode 100644
index 00000000000..9850795c9b4
--- /dev/null
+++ b/doc/user/project/merge_requests/img/checkout_button.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index f479f9e4ef6..9015a964781 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -92,6 +92,15 @@ request widget will show the "Removes source branch" text.
![Remove source branch status](img/remove_source_branch_status.png)
+## Allow collaboration on merge requests across forks
+
+When a user opens a merge request from a fork, they are given the option to allow
+upstream maintainers to collaborate with them on the source branch. This allows
+the maintainers of the upstream project to make small fixes or rebase branches
+before merging, reducing the back and forth of accepting community contributions.
+
+[Learn more about allowing upstream members to push to forks.](allow_collaboration.md)
+
## Authorization for merge requests
There are two main ways to have a merge request flow with GitLab:
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 290dfa5af84..595241b2cba 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -85,6 +85,12 @@ and a project within this group is called `blog`. Your project
URL is `https://gitlab.com/websites/blog/`. Once you enable
GitLab Pages for this project, the site will live under
`https://websites.gitlab.io/blog/`.
+- You created a group for your engineering department called `engineering`,
+a subgroup for all your documentation websites called `docs`,
+and a project within this subgroup is called `workflows`. Your project
+URL is `https://gitlab.com/engineering/docs/workflows/`. Once you enable
+GitLab Pages for this project, the site will live under
+`https://engineering.gitlab.io/docs/workflows`.
#### User and Group Websites
@@ -97,9 +103,7 @@ will be published under `https://john.gitlab.io`.
Once you enable GitLab Pages for your project,
your website will be published under `https://websites.gitlab.io`.
->**Note:**
-GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
-You can only create the highest level group website.
+> Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
**General example:**
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index ed049e2e648..a7846b1ee18 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -38,6 +38,7 @@ be served on.
| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` |
| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` |
| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`|
+| Project pages owned by a subgroup | `subgroup/projectname` | `http(s)://groupname.example.io/subgroup/projectname`|
> **Warning:**
> There are some known [limitations](#limitations) regarding namespaces served
@@ -494,8 +495,8 @@ don't redirect HTTP to HTTPS.
[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC"
-GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
-You can only create the highest level group website.
+GitLab Pages [does **not** support group websites for subgroups](../../group/subgroups/index.md#limitations).
+You can only create the highest-level group website.
## Redirects in GitLab Pages
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 051277dfe02..ec8b8444d99 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -3,7 +3,7 @@
> **Notes**:
> - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533].
> - In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853].
-> - Cron notation is parsed by [Rufus-Scheduler](https://github.com/jmettraux/rufus-scheduler).
+> - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
Pipeline schedules can be used to run a pipeline at specific intervals, for example every
month on the 22nd for a certain branch.
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 88d745b0ce4..5aa26af5265 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -157,7 +157,27 @@ into your `README.md`:
![coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)
```
-### Environment Variables
+### Badge styles
+
+Pipeline badges can be rendered in different styles by adding the `style=style_name` parameter to the URL. Currently two styles are available:
+
+#### Flat (default)
+
+```
+https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat
+```
+
+![Badge flat style](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage&style=flat)
+
+#### Flat square
+
+```
+https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat-square
+```
+
+![Badge flat square style](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage&style=flat-square)
+
+## Environment Variables
[Environment variables](../../../ci/variables/README.html#variables) can be set in an environment to be available to a runner.
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index 783081cec26..f05554ffc5b 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -2,16 +2,17 @@
Read through GiLab's branching documentation:
-- [Create a branch](../web_editor.md#create-a-new-branch)
-- [Default branch](#default-branch)
-- [Protected branches](../../protected_branches.md#protected-branches)
-- [Delete merged branches](#delete-merged-branches)
-- [Branch filter search box](#branch-filter-search-box)
+- [Create a branch](../web_editor.md#create-a-new-branch).
+- [Default branch](#default-branch).
+- [Protected branches](../../protected_branches.md#protected-branches).
+- [Delete merged branches](#delete-merged-branches).
+- [Branch filter search box](#branch-filter-search-box).
See also:
-- [GitLab Flow](../../../../university/training/gitlab_flow.md#gitlab-flow): use the best of GitLab for your branching strategies
-- [Getting started with Git](../../../../topics/git/index.md) and GitLab
+- [Branches API](../../../../api/branches.md), for information on operating on repository branches using the GitLab API.
+- [GitLab Flow](../../../../university/training/gitlab_flow.md#gitlab-flow). Use the best of GitLab for your branching strategies.
+- [Getting started with Git](../../../../topics/git/index.md) and GitLab.
## Default branch
@@ -41,7 +42,6 @@ this operation.
It's particularly useful to clean up old branches that were not deleted
automatically when a merge request was merged.
-
## Branch filter search box
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166) in GitLab 11.5.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 59b67c67f9d..a768b78cda5 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -110,6 +110,7 @@ module API
mount ::API::GroupMilestones
mount ::API::Groups
mount ::API::GroupVariables
+ mount ::API::ImportGithub
mount ::API::Internal
mount ::API::Issues
mount ::API::JobArtifacts
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index f13741f80d2..97b421e8651 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -115,6 +115,9 @@ module API
expose :group_name do |group_link, options|
group_link.group.name
end
+ expose :group_full_path do |group_link, options|
+ group_link.group.full_path
+ end
expose :group_access, as: :group_access_level
expose :expires_at
end
@@ -277,7 +280,7 @@ module API
# N+1 is solved then by using `subject.tags.map(&:name)`
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
super(projects_relation).preload(:group)
- .preload(project_group_links: :group,
+ .preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,
forked_from_project: [:route, :forks, :tags, namespace: :route])
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 1331248699f..835aac05905 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -16,15 +16,13 @@ module API
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def gate_targets(params)
- targets = []
- targets << Feature.group(params[:feature_group]) if params[:feature_group]
- targets << UserFinder.new(params[:user]).find_by_username if params[:user]
+ Feature::Target.new(params).targets
+ end
- targets
+ def gate_specified?(params)
+ Feature::Target.new(params).gate_specified?
end
- # rubocop: enable CodeReuse/ActiveRecord
end
resource :features do
@@ -44,6 +42,7 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
optional :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username'
+ optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce'
end
post ':name' do
feature = Feature.get(params[:name])
@@ -52,13 +51,13 @@ module API
case value
when true
- if targets.present?
+ if gate_specified?(params)
targets.each { |target| feature.enable(target) }
else
feature.enable
end
when false
- if targets.present?
+ if gate_specified?(params)
targets.each { |target| feature.disable(target) }
else
feature.disable
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
new file mode 100644
index 00000000000..bb4e536cf57
--- /dev/null
+++ b/lib/api/import_github.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module API
+ class ImportGithub < Grape::API
+ rescue_from Octokit::Unauthorized, with: :provider_unauthorized
+
+ helpers do
+ def client
+ @client ||= Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options)
+ end
+
+ def access_params
+ { github_access_token: params[:personal_access_token] }
+ end
+
+ def client_options
+ {}
+ end
+
+ def provider
+ :github
+ end
+ end
+
+ desc 'Import a GitHub project' do
+ detail 'This feature was introduced in GitLab 11.3.4.'
+ success Entities::ProjectEntity
+ end
+ params do
+ requires :personal_access_token, type: String, desc: 'GitHub personal access token'
+ requires :repo_id, type: Integer, desc: 'GitHub repository ID'
+ optional :new_name, type: String, desc: 'New repo name'
+ requires :target_namespace, type: String, desc: 'Namespace to import repo into'
+ end
+ post 'import/github' do
+ result = Import::GithubService.new(client, current_user, params).execute(access_params, provider)
+
+ if result[:status] == :success
+ present ProjectSerializer.new.represent(result[:project])
+ else
+ status result[:http_status]
+ { errors: result[:message] }
+ end
+ end
+ end
+end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index f53ba0ab761..95371961398 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -35,7 +35,7 @@ module API
end
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
- optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master'
+ optional :default_branch_protection, type: Integer, values: Gitlab::Access.protection_values, desc: 'Determine if developers can push to master'
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 302b2797a34..ef0e3decc2c 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -22,7 +22,9 @@ module API
end
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ WIKI_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(slug: API::NO_SLASH_URL_PART_REGEX)
+
+ resource :projects, requirements: WIKI_ENDPOINT_REQUIREMENTS do
desc 'Get a list of wiki pages' do
success Entities::WikiPageBasic
end
@@ -103,7 +105,7 @@ module API
requires :file, type: ::API::Validations::Types::SafeFile, desc: 'The attachment file to be uploaded'
optional :branch, type: String, desc: 'The name of the branch'
end
- post ":id/wikis/attachments", requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ post ":id/wikis/attachments" do
authorize! :create_wiki, user_project
result = ::Wikis::CreateAttachmentService.new(user_project,
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index 0032ae8f84b..2bac84846c5 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -71,8 +71,14 @@ module Backup
end
def run_pipeline!(cmd_list, options = {})
- status_list = Open3.pipeline(*cmd_list, options)
- raise Backup::Error, 'Backup failed' unless status_list.compact.all?(&:success?)
+ err_r, err_w = IO.pipe
+ options[:err] = err_w
+ status = Open3.pipeline(*cmd_list, options)
+ err_w.close
+ return if status.compact.all?(&:success?)
+
+ regex = /^g?tar: \.: Cannot mkdir: No such file or directory$/
+ raise Backup::Error, 'Backup failed' unless err_r.read =~ regex
end
end
end
diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb
new file mode 100644
index 00000000000..97527976437
--- /dev/null
+++ b/lib/banzai/filter/footnote_filter.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML Filter for footnotes
+ #
+ # Footnotes are supported in CommonMark. However we were stripping
+ # the ids during sanitization. Those are now allowed.
+ #
+ # Footnotes are numbered the same - the first one has `id=fn1`, the
+ # second is `id=fn2`, etc. In order to allow footnotes when rendering
+ # multiple markdown blocks on a page, we need to make each footnote
+ # reference unique.
+ #
+ # This filter adds a random number to each footnote (the same number
+ # can be used for a single render). So you get `id=fn1-4335` and `id=fn2-4335`.
+ #
+ class FootnoteFilter < HTML::Pipeline::Filter
+ INTEGER_PATTERN = /\A\d+\z/.freeze
+ FOOTNOTE_ID_PREFIX = 'fn'.freeze
+ FOOTNOTE_LINK_ID_PREFIX = 'fnref'.freeze
+ FOOTNOTE_LI_REFERENCE_PATTERN = /\A#{FOOTNOTE_ID_PREFIX}\d+\z/.freeze
+ FOOTNOTE_LINK_REFERENCE_PATTERN = /\A#{FOOTNOTE_LINK_ID_PREFIX}\d+\z/.freeze
+ FOOTNOTE_START_NUMBER = 1
+
+ def call
+ return doc unless first_footnote = doc.at_css("ol > li[id=#{fn_id(FOOTNOTE_START_NUMBER)}]")
+
+ # Sanitization stripped off the section wrapper - add it back in
+ first_footnote.parent.wrap('<section class="footnotes">')
+ rand_suffix = "-#{random_number}"
+
+ doc.css('sup > a[id]').each do |link_node|
+ ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
+ footnote_node = doc.at_css("li[id=#{fn_id(ref_num)}]")
+ backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]")
+
+ if ref_num =~ INTEGER_PATTERN && footnote_node && backref_node
+ link_node[:href] += rand_suffix
+ link_node[:id] += rand_suffix
+ footnote_node[:id] += rand_suffix
+ backref_node[:href] += rand_suffix
+
+ # Sanitization stripped off class - add it back in
+ link_node.parent.append_class('footnote-ref')
+ backref_node.append_class('footnote-backref')
+ end
+ end
+
+ doc
+ end
+
+ private
+
+ def random_number
+ @random_number ||= rand(10000)
+ end
+
+ def fn_id(num)
+ "#{FOOTNOTE_ID_PREFIX}#{num}"
+ end
+
+ def fnref_id(num)
+ "#{FOOTNOTE_LINK_ID_PREFIX}#{num}"
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index c70c3f0c04e..fce042e8946 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -101,9 +101,9 @@ module Banzai
def self_and_ancestors_ids(parent)
if group_context?(parent)
- parent.self_and_ancestors_ids
+ parent.self_and_ancestors.select(:id)
elsif project_context?(parent)
- parent.group&.self_and_ancestors_ids
+ parent.group&.self_and_ancestors&.select(:id)
end
end
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 8ba09290e6d..edc053638a8 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -8,8 +8,8 @@ module Banzai
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
include Gitlab::Utils::StrongMemoize
- UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
- TABLE_ALIGNMENT_PATTERN = /text-align: (?<alignment>center|left|right)/
+ UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
+ TABLE_ALIGNMENT_PATTERN = /text-align: (?<alignment>center|left|right)/.freeze
def whitelist
strong_memoize(:whitelist) do
@@ -45,10 +45,9 @@ module Banzai
whitelist[:attributes][:all].delete('name')
whitelist[:attributes]['a'].push('name')
- # Allow any protocol in `a` elements...
+ # Allow any protocol in `a` elements
+ # and then remove links with unsafe protocols
whitelist[:protocols].delete('a')
-
- # ...but then remove links with unsafe protocols
whitelist[:transformers].push(self.class.remove_unsafe_links)
# Remove `rel` attribute from `a` elements
@@ -57,6 +56,12 @@ module Banzai
# Remove any `style` properties not required for table alignment
whitelist[:transformers].push(self.class.remove_unsafe_table_style)
+ # Allow `id` in a and li elements for footnotes
+ # and remove any `id` properties not matching for footnotes
+ whitelist[:attributes]['a'].push('id')
+ whitelist[:attributes]['li'] = %w(id)
+ whitelist[:transformers].push(self.class.remove_non_footnote_ids)
+
whitelist
end
@@ -112,6 +117,20 @@ module Banzai
end
end
end
+
+ def remove_non_footnote_ids
+ lambda do |env|
+ node = env[:node]
+
+ return unless node.name == 'a' || node.name == 'li'
+ return unless node.has_attribute?('id')
+
+ return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN
+ return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN
+
+ node.remove_attribute('id')
+ end
+ end
end
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 5f13a6d6cde..d860dad0b6c 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -30,6 +30,7 @@ module Banzai
Filter::AutolinkFilter,
Filter::ExternalLinkFilter,
Filter::SuggestionFilter,
+ Filter::FootnoteFilter,
*reference_filters,
diff --git a/lib/feature.rb b/lib/feature.rb
index e048a443abc..e59cd70f822 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -102,4 +102,42 @@ class Feature
expires_in: 1.hour)
end
end
+
+ class Target
+ attr_reader :params
+
+ def initialize(params)
+ @params = params
+ end
+
+ def gate_specified?
+ %i(user project feature_group).any? { |key| params.key?(key) }
+ end
+
+ def targets
+ [feature_group, user, project].compact
+ end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def feature_group
+ return unless params.key?(:feature_group)
+
+ Feature.group(params[:feature_group])
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def user
+ return unless params.key?(:user)
+
+ UserFinder.new(params[:user]).find_by_username!
+ end
+
+ def project
+ return unless params.key?(:project)
+
+ Project.find_by_full_path(params[:project])
+ end
+ end
end
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index 074d04fc32a..b91394f7f58 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -89,4 +89,12 @@ module Gitlab
def self.dev_env_or_com?
Rails.env.development? || org? || com?
end
+
+ def self.process_name
+ return 'sidekiq' if Sidekiq.server?
+ return 'console' if defined?(Rails::Console)
+ return 'test' if Rails.env.test?
+
+ 'web'
+ end
end
diff --git a/lib/gitlab/access/branch_protection.rb b/lib/gitlab/access/branch_protection.rb
new file mode 100644
index 00000000000..f039e5c011f
--- /dev/null
+++ b/lib/gitlab/access/branch_protection.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Access
+ # A wrapper around Integer based branch protection levels.
+ #
+ # This wrapper can be used to work with branch protection levels without
+ # having to directly refer to the constants. For example, instead of this:
+ #
+ # if access_level == Gitlab::Access::PROTECTION_DEV_CAN_PUSH
+ # ...
+ # end
+ #
+ # You can write this instead:
+ #
+ # protection = BranchProtection.new(access_level)
+ #
+ # if protection.developer_can_push?
+ # ...
+ # end
+ class BranchProtection
+ attr_reader :level
+
+ # @param [Integer] level The branch protection level as an Integer.
+ def initialize(level)
+ @level = level
+ end
+
+ def any?
+ level != PROTECTION_NONE
+ end
+
+ def developer_can_push?
+ level == PROTECTION_DEV_CAN_PUSH
+ end
+
+ def developer_can_merge?
+ level == PROTECTION_DEV_CAN_MERGE
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index a0244a3cea1..48d134f91b0 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -98,9 +98,7 @@ module Gitlab
private
- def entry
- @entry
- end
+ attr_reader :entry
def config
@config ||= Gitlab::Auth::LDAP::Config.new(provider)
diff --git a/lib/gitlab/background_migration/backfill_project_repositories.rb b/lib/gitlab/background_migration/backfill_project_repositories.rb
index aaf520d70f6..c8d83cc1803 100644
--- a/lib/gitlab/background_migration/backfill_project_repositories.rb
+++ b/lib/gitlab/background_migration/backfill_project_repositories.rb
@@ -83,7 +83,7 @@ module Gitlab
extend ActiveSupport::Concern
def full_path
- @full_path ||= build_full_path
+ route&.path || build_full_path
end
def build_full_path
@@ -99,7 +99,12 @@ module Gitlab
end
end
- # Namespace model.
+ # Route model
+ class Route < ActiveRecord::Base
+ belongs_to :source, inverse_of: :route, polymorphic: true
+ end
+
+ # Namespace model
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
self.inheritance_column = nil
@@ -108,6 +113,8 @@ module Gitlab
belongs_to :parent, class_name: 'Namespace', inverse_of: 'namespaces'
+ has_one :route, -> { where(source_type: 'Namespace') }, inverse_of: :source, foreign_key: :source_id
+
has_many :projects, inverse_of: :parent
has_many :namespaces, inverse_of: :parent
end
@@ -134,6 +141,7 @@ module Gitlab
belongs_to :parent, class_name: 'Namespace', foreign_key: :namespace_id, inverse_of: 'projects'
+ has_one :route, -> { where(source_type: 'Project') }, inverse_of: :source, foreign_key: :source_id
has_one :project_repository, inverse_of: :project
delegate :disk_path, to: :storage
@@ -194,6 +202,8 @@ module Gitlab
def project_repositories(start_id, stop_id)
projects
.without_project_repository
+ .includes(:route, parent: [:route]).references(:routes)
+ .includes(:parent).references(:namespaces)
.where(id: start_id..stop_id)
.map { |project| build_attributes_for_project(project) }
.compact
diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb
index b1db9084662..94f4a4e36c9 100644
--- a/lib/gitlab/ci/cron_parser.rb
+++ b/lib/gitlab/ci/cron_parser.rb
@@ -35,7 +35,7 @@ module Gitlab
# NOTE:
# cron_timezone can only accept timezones listed in TZInfo::Timezone.
# Aliases of Timezones from ActiveSupport::TimeZone are NOT accepted,
- # because Rufus::Scheduler only supports TZInfo::Timezone.
+ # because Fugit::Cron only supports TZInfo::Timezone.
#
# For example, those codes have the same effect.
# Time.zone = 'Pacific Time (US & Canada)' (ActiveSupport::TimeZone)
@@ -47,10 +47,7 @@ module Gitlab
# If you want to know more, please take a look
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
def try_parse_cron(cron, cron_timezone)
- cron_line = Rufus::Scheduler.parse("#{cron} #{cron_timezone}")
- cron_line if cron_line.is_a?(Rufus::Scheduler::CronLine)
- rescue
- # noop
+ Fugit::Cron.parse("#{cron} #{cron_timezone}")
end
end
end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index de4288fb532..95160e1432f 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -667,6 +667,14 @@ rollout 100%:
create_application_secret "$track"
+ env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]')
+ eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS
+ if [ -n "$env_ADDITIONAL_HOSTS" ]; then
+ additional_hosts="{$env_ADDITIONAL_HOSTS}"
+ elif [ -n "$ADDITIONAL_HOSTS" ]; then
+ additional_hosts="{$ADDITIONAL_HOSTS}"
+ fi
+
if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
echo "Deploying first release with database initialization..."
helm upgrade --install \
@@ -681,6 +689,7 @@ rollout 100%:
--set application.database_url="$DATABASE_URL" \
--set application.secretName="$APPLICATION_SECRET_NAME" \
--set service.url="$CI_ENVIRONMENT_URL" \
+ --set service.additionalHosts="$additional_hosts" \
--set replicaCount="$replicas" \
--set postgresql.enabled="$postgres_enabled" \
--set postgresql.nameOverride="postgres" \
@@ -714,6 +723,7 @@ rollout 100%:
--set application.database_url="$DATABASE_URL" \
--set application.secretName="$APPLICATION_SECRET_NAME" \
--set service.url="$CI_ENVIRONMENT_URL" \
+ --set service.additionalHosts="$additional_hosts" \
--set replicaCount="$replicas" \
--set postgresql.enabled="$postgres_enabled" \
--set postgresql.nameOverride="postgres" \
diff --git a/lib/gitlab/git/bundle_file.rb b/lib/gitlab/git/bundle_file.rb
new file mode 100644
index 00000000000..8384a436fcc
--- /dev/null
+++ b/lib/gitlab/git/bundle_file.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ class BundleFile
+ # All git bundle files start with this string
+ #
+ # https://github.com/git/git/blob/v2.20.1/bundle.c#L15
+ MAGIC = "# v2 git bundle\n"
+
+ InvalidBundleError = Class.new(StandardError)
+
+ attr_reader :filename
+
+ def self.check!(filename)
+ new(filename).check!
+ end
+
+ def initialize(filename)
+ @filename = filename
+ end
+
+ def check!
+ data = File.open(filename, 'r') { |f| f.read(MAGIC.size) }
+
+ raise InvalidBundleError, 'Invalid bundle file' unless data == MAGIC
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 5bbedc9d5e3..786c90f9272 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -789,6 +789,11 @@ module Gitlab
end
def create_from_bundle(bundle_path)
+ # It's important to check that the linked-to file is actually a valid
+ # .bundle file as it is passed to `git clone`, which may otherwise
+ # interpret it as a pointer to another repository
+ ::Gitlab::Git::BundleFile.check!(bundle_path)
+
gitaly_repository_client.create_from_bundle(bundle_path)
end
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
index f142f9da43d..817db12ac55 100644
--- a/lib/gitlab/middleware/read_only/controller.rb
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -71,12 +71,16 @@ module Gitlab
@route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {}
end
+ def relative_url
+ File.join('', Gitlab.config.gitlab.relative_url_root).chomp('/')
+ end
+
# Overridden in EE module
def whitelisted_routes
- grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route
+ grack_route? || internal_route? || lfs_route? || sidekiq_route?
end
- def grack_route
+ def grack_route?
# Calling route_hash may be expensive. Only do it if we think there's a possible match
return false unless
request.path.end_with?('.git/git-upload-pack', '.git/git-receive-pack')
@@ -84,7 +88,11 @@ module Gitlab
WHITELISTED_GIT_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
- def lfs_route
+ def internal_route?
+ ReadOnly.internal_routes.any? { |path| request.path.include?(path) }
+ end
+
+ def lfs_route?
# Calling route_hash may be expensive. Only do it if we think there's a possible match
unless request.path.end_with?('/info/lfs/objects/batch',
'/info/lfs/locks', '/info/lfs/locks/verify') ||
@@ -95,8 +103,8 @@ module Gitlab
WHITELISTED_GIT_LFS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
- def sidekiq_route
- request.path.start_with?('/admin/sidekiq')
+ def sidekiq_route?
+ request.path.start_with?("#{relative_url}/admin/sidekiq")
end
end
end
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 46d01964eac..956c16117f5 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -52,14 +52,6 @@ module Gitlab
end
end
- def self.program_context
- if Sidekiq.server?
- 'sidekiq'
- else
- 'rails'
- end
- end
-
def self.should_raise_for_dev?
Rails.env.development? || Rails.env.test?
end
diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb
new file mode 100644
index 00000000000..3c4db42ac06
--- /dev/null
+++ b/lib/gitlab/tracing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Tracing
+ # Only enable tracing when the `GITLAB_TRACING` env var is configured. Note that we avoid using ApplicationSettings since
+ # the same environment variable needs to be configured for Workhorse, Gitaly and any other components which
+ # emit tracing. Since other components may start before Rails, and may not have access to ApplicationSettings,
+ # an env var makes more sense.
+ def self.enabled?
+ connection_string.present?
+ end
+
+ def self.connection_string
+ ENV['GITLAB_TRACING']
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/factory.rb b/lib/gitlab/tracing/factory.rb
new file mode 100644
index 00000000000..fc714164353
--- /dev/null
+++ b/lib/gitlab/tracing/factory.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require "cgi"
+
+module Gitlab
+ module Tracing
+ class Factory
+ OPENTRACING_SCHEME = "opentracing"
+
+ def self.create_tracer(service_name, connection_string)
+ return unless connection_string.present?
+
+ begin
+ opentracing_details = parse_connection_string(connection_string)
+ driver_name = opentracing_details[:driver_name]
+
+ case driver_name
+ when "jaeger"
+ JaegerFactory.create_tracer(service_name, opentracing_details[:options])
+ else
+ raise "Unknown driver: #{driver_name}"
+ end
+ rescue => e
+ # Can't create the tracer? Warn and continue sans tracer
+ warn "Unable to instantiate tracer: #{e}"
+ nil
+ end
+ end
+
+ def self.parse_connection_string(connection_string)
+ parsed = URI.parse(connection_string)
+
+ unless valid_uri?(parsed)
+ raise "Invalid tracing connection string"
+ end
+
+ {
+ driver_name: parsed.host,
+ options: parse_query(parsed.query)
+ }
+ end
+ private_class_method :parse_connection_string
+
+ def self.parse_query(query)
+ return {} unless query
+
+ CGI.parse(query).symbolize_keys.transform_values(&:first)
+ end
+ private_class_method :parse_query
+
+ def self.valid_uri?(uri)
+ return false unless uri
+
+ uri.scheme == OPENTRACING_SCHEME &&
+ uri.host.to_s =~ /^[a-z0-9_]+$/ &&
+ uri.path.empty?
+ end
+ private_class_method :valid_uri?
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/jaeger_factory.rb b/lib/gitlab/tracing/jaeger_factory.rb
new file mode 100644
index 00000000000..2682007302a
--- /dev/null
+++ b/lib/gitlab/tracing/jaeger_factory.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'jaeger/client'
+
+module Gitlab
+ module Tracing
+ class JaegerFactory
+ # When the probabilistic sampler is used, by default 0.1% of requests will be traced
+ DEFAULT_PROBABILISTIC_RATE = 0.001
+
+ # The default port for the Jaeger agent UDP listener
+ DEFAULT_UDP_PORT = 6831
+
+ # Reduce this from default of 10 seconds as the Ruby jaeger
+ # client doesn't have overflow control, leading to very large
+ # messages which fail to send over UDP (max packet = 64k)
+ # Flush more often, with smaller packets
+ FLUSH_INTERVAL = 5
+
+ def self.create_tracer(service_name, options)
+ kwargs = {
+ service_name: service_name,
+ sampler: get_sampler(options[:sampler], options[:sampler_param]),
+ reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint])
+ }.compact
+
+ extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug) # rubocop: disable CodeReuse/ActiveRecord
+ if extra_params.present?
+ message = "jaeger tracer: invalid option: #{extra_params.keys.join(", ")}"
+
+ if options[:strict_parsing]
+ raise message
+ else
+ warn message
+ end
+ end
+
+ Jaeger::Client.build(kwargs)
+ end
+
+ def self.get_sampler(sampler_type, sampler_param)
+ case sampler_type
+ when "probabilistic"
+ sampler_rate = sampler_param ? sampler_param.to_f : DEFAULT_PROBABILISTIC_RATE
+ Jaeger::Samplers::Probabilistic.new(rate: sampler_rate)
+ when "const"
+ const_value = sampler_param == "1"
+ Jaeger::Samplers::Const.new(const_value)
+ else
+ nil
+ end
+ end
+ private_class_method :get_sampler
+
+ def self.get_reporter(service_name, http_endpoint, udp_endpoint)
+ encoder = Jaeger::Encoders::ThriftEncoder.new(service_name: service_name)
+
+ if http_endpoint.present?
+ sender = get_http_sender(encoder, http_endpoint)
+ elsif udp_endpoint.present?
+ sender = get_udp_sender(encoder, udp_endpoint)
+ else
+ return nil
+ end
+
+ Jaeger::Reporters::RemoteReporter.new(
+ sender: sender,
+ flush_interval: FLUSH_INTERVAL
+ )
+ end
+ private_class_method :get_reporter
+
+ def self.get_http_sender(encoder, address)
+ Jaeger::HttpSender.new(
+ url: address,
+ encoder: encoder,
+ logger: Logger.new(STDOUT)
+ )
+ end
+ private_class_method :get_http_sender
+
+ def self.get_udp_sender(encoder, address)
+ pair = address.split(":", 2)
+ host = pair[0]
+ port = pair[1] ? pair[1].to_i : DEFAULT_UDP_PORT
+
+ Jaeger::UdpSender.new(
+ host: host,
+ port: port,
+ encoder: encoder,
+ logger: Logger.new(STDOUT)
+ )
+ end
+ private_class_method :get_udp_sender
+ end
+ end
+end
diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb
index e06245294c4..46aad8aa885 100644
--- a/lib/system_check/base_check.rb
+++ b/lib/system_check/base_check.rb
@@ -70,18 +70,14 @@ module SystemCheck
# multiple reasons why a check can fail
#
# @param [String] reason to be displayed
- def skip_reason=(reason)
- @skip_reason = reason
- end
+ attr_writer :skip_reason
# Skip reason defined during runtime
#
# This value have precedence over the one defined in the subclass
#
# @return [String] the reason
- def skip_reason
- @skip_reason
- end
+ attr_reader :skip_reason
# Does the check support automatically repair routine?
#
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 329d258cd09..795a46382a7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1473,6 +1473,9 @@ msgstr ""
msgid "Closed"
msgstr ""
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
@@ -1899,6 +1902,9 @@ msgstr ""
msgid "ClusterIntegration|sign up"
msgstr ""
+msgid "Code"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
@@ -1937,9 +1943,15 @@ msgid_plural "Commits"
msgstr[0] ""
msgstr[1] ""
+msgid "Commit %{commit_id}"
+msgstr ""
+
msgid "Commit Message"
msgstr ""
+msgid "Commit deleted"
+msgstr ""
+
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
@@ -2779,6 +2791,9 @@ msgstr ""
msgid "Enable the Performance Bar for a given group."
msgstr ""
+msgid "Enable two-factor authentication"
+msgstr ""
+
msgid "Enable usage ping"
msgstr ""
@@ -3151,6 +3166,12 @@ msgstr ""
msgid "Filter by two-factor authentication"
msgstr ""
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
msgid "Filter..."
msgstr ""
@@ -3415,6 +3436,9 @@ msgstr ""
msgid "Group name"
msgstr ""
+msgid "Group:"
+msgstr ""
+
msgid "Group: %{group_name}"
msgstr ""
@@ -4150,6 +4174,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage two-factor authentication"
+msgstr ""
+
msgid "Manifest"
msgstr ""
@@ -4910,6 +4937,9 @@ msgstr ""
msgid "Personal Access Token"
msgstr ""
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
msgid "Pick a name"
msgstr ""
@@ -5189,6 +5219,12 @@ msgstr ""
msgid "Profiles|Account scheduled for removal."
msgstr ""
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
msgid "Profiles|Add key"
msgstr ""
@@ -5204,6 +5240,9 @@ msgstr ""
msgid "Profiles|Change username"
msgstr ""
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
msgid "Profiles|Choose file..."
msgstr ""
@@ -5216,6 +5255,15 @@ msgstr ""
msgid "Profiles|Clear status"
msgstr ""
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
msgid "Profiles|Current path: %{path}"
msgstr ""
@@ -5234,6 +5282,9 @@ msgstr ""
msgid "Profiles|Deleting an account has the following effects:"
msgstr ""
+msgid "Profiles|Disconnect"
+msgstr ""
+
msgid "Profiles|Do not show on profile"
msgstr ""
@@ -5246,6 +5297,9 @@ msgstr ""
msgid "Profiles|Enter your name, so people you know can recognize you"
msgstr ""
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
msgid "Profiles|Invalid password"
msgstr ""
@@ -5282,6 +5336,9 @@ msgstr ""
msgid "Profiles|Set new profile picture"
msgstr ""
+msgid "Profiles|Social sign-in"
+msgstr ""
+
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr ""
@@ -5309,6 +5366,9 @@ msgstr ""
msgid "Profiles|This information will appear on your profile"
msgstr ""
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr ""
@@ -5450,6 +5510,9 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project:"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -6012,6 +6075,9 @@ msgstr ""
msgid "Search for projects, issues, etc."
msgstr ""
+msgid "Search groups"
+msgstr ""
+
msgid "Search merge requests"
msgstr ""
@@ -6027,6 +6093,9 @@ msgstr ""
msgid "Search project"
msgstr ""
+msgid "Search projects"
+msgstr ""
+
msgid "Search users"
msgstr ""
@@ -6323,6 +6392,9 @@ msgstr ""
msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
msgstr ""
+msgid "Snippet Contents"
+msgstr ""
+
msgid "Snippets"
msgstr ""
@@ -7269,6 +7341,9 @@ msgstr ""
msgid "Title"
msgstr ""
+msgid "Titles and Filenames"
+msgstr ""
+
msgid "To GitLab"
msgstr ""
@@ -7692,6 +7767,9 @@ msgstr ""
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
+msgid "We couldn't find any results matching"
+msgstr ""
+
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -8073,6 +8151,9 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
msgid "Your projects"
msgstr ""
@@ -8097,6 +8178,9 @@ msgstr ""
msgid "command line instructions"
msgstr ""
+msgid "commented on %{link_to_project}"
+msgstr ""
+
msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
msgstr ""
@@ -8170,6 +8254,12 @@ msgstr ""
msgid "importing"
msgstr ""
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
msgid "issue boards"
msgstr ""
@@ -8422,6 +8512,9 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "private"
+msgstr ""
+
msgid "project"
msgstr ""
diff --git a/package.json b/package.json
index c4f749fb2c6..75df0ec3ff6 100644
--- a/package.json
+++ b/package.json
@@ -161,7 +161,7 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.18.4",
- "prettier": "1.15.2",
+ "prettier": "1.15.3",
"vue-jest": "^3.0.2",
"webpack-dev-server": "^3.1.14",
"yarn-deduplicate": "^1.0.5"
diff --git a/qa/Gemfile b/qa/Gemfile
index 75ad7bd07af..873eac1013f 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -7,4 +7,4 @@ gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13'
-gem 'nokogiri', '~> 1.8.5'
+gem 'nokogiri', '~> 1.10.1'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 55f3211482b..419cacdb2af 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -44,11 +44,11 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mini_mime (1.0.0)
- mini_portile2 (2.3.0)
+ mini_portile2 (2.4.0)
minitest (5.11.1)
netrc (0.11.0)
- nokogiri (1.8.5)
- mini_portile2 (~> 2.3.0)
+ nokogiri (1.10.1)
+ mini_portile2 (~> 2.4.0)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@@ -97,7 +97,7 @@ DEPENDENCIES
airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
- nokogiri (~> 1.8.5)
+ nokogiri (~> 1.10.0)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
diff --git a/qa/Rakefile b/qa/Rakefile
new file mode 100644
index 00000000000..8df1cfdc174
--- /dev/null
+++ b/qa/Rakefile
@@ -0,0 +1,6 @@
+require_relative 'qa/tools/revoke_all_personal_access_tokens'
+
+desc "Revokes all personal access tokens"
+task :revoke_personal_access_tokens do
+ QA::Tools::RevokeAllPersonalAccessTokens.new.run
+end
diff --git a/qa/qa.rb b/qa/qa.rb
index ef6a92f9768..2cbd74121f1 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -158,6 +158,10 @@ module QA
autoload :Activity, 'qa/page/project/activity'
autoload :Menu, 'qa/page/project/menu'
+ module Branches
+ autoload :Show, 'qa/page/project/branches/show'
+ end
+
module Commit
autoload :Show, 'qa/page/project/commit/show'
end
@@ -191,6 +195,11 @@ module QA
autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories'
end
+ module SubMenus
+ autoload :Common, 'qa/page/project/sub_menus/common'
+ autoload :Repository, 'qa/page/project/sub_menus/repository'
+ end
+
module Issue
autoload :New, 'qa/page/project/issue/new'
autoload :Show, 'qa/page/project/issue/show'
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 7f959441dac..86e00cdbb9c 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -46,12 +46,9 @@ module QA
run("git clone #{opts} #{uri} ./")
end
- def checkout(branch_name)
- run(%Q{git checkout "#{branch_name}"})
- end
-
- def checkout_new_branch(branch_name)
- run(%Q{git checkout -b "#{branch_name}"})
+ def checkout(branch_name, new_branch: false)
+ opts = new_branch ? '-b' : ''
+ run(%Q{git checkout #{opts} "#{branch_name}"}).to_s
end
def shallow_clone
@@ -84,6 +81,10 @@ module QA
run("git push #{uri} #{branch}")
end
+ def merge(branch)
+ run("git merge #{branch}")
+ end
+
def commits
run('git log --oneline').split("\n")
end
diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb
index 9191dbe9cf3..8c12eff5cf1 100644
--- a/qa/qa/page/profile/personal_access_tokens.rb
+++ b/qa/qa/page/profile/personal_access_tokens.rb
@@ -3,29 +3,51 @@ module QA
module Profile
class PersonalAccessTokens < Page::Base
view 'app/views/shared/_personal_access_tokens_form.html.haml' do
- element :personal_access_token_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
- element :create_token_button, 'submit "Create #{type} token"' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
- element :scopes_api_radios, "label :scopes" # rubocop:disable QA/ElementWithPattern
+ element :personal_access_token_name_field
+ element :create_token_button
+ end
+
+ view 'app/views/shared/tokens/_scopes_form.html.haml' do
+ element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
view 'app/views/shared/_personal_access_tokens_created_container.html.haml' do
- element :create_token_field, "text_field_tag 'created-personal-access-token'" # rubocop:disable QA/ElementWithPattern
+ element :created_personal_access_token
+ end
+ view 'app/views/shared/_personal_access_tokens_table.html.haml' do
+ element :revoke_button
end
def fill_token_name(name)
- fill_in 'personal_access_token_name', with: name
+ fill_element(:personal_access_token_name_field, name)
end
def check_api
- check 'personal_access_token_scopes_api'
+ check_element(:api_radio)
end
def create_token
- click_on 'Create personal access token'
+ click_element(:create_token_button)
end
def created_access_token
- page.find('#created-personal-access-token').value
+ find_element(:created_personal_access_token, wait: 30).value
+ end
+
+ def has_token_row_for_name?(token_name)
+ page.has_css?('tr', text: token_name, wait: 1.0)
+ end
+
+ def first_token_row_for_name(token_name)
+ page.find('tr', text: token_name, match: :first, wait: 1.0)
+ end
+
+ def revoke_first_token_with_name(token_name)
+ within first_token_row_for_name(token_name) do
+ accept_confirm do
+ click_element(:revoke_button)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/branches/show.rb b/qa/qa/page/project/branches/show.rb
new file mode 100644
index 00000000000..762a97e2088
--- /dev/null
+++ b/qa/qa/page/project/branches/show.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Branches
+ class Show < Page::Base
+ view 'app/views/projects/branches/_branch.html.haml' do
+ element :remove_btn
+ end
+ view 'app/views/projects/branches/_panel.html.haml' do
+ element :all_branches
+ end
+ view 'app/views/projects/branches/index.html.haml' do
+ element :delete_merged_branches
+ end
+
+ def delete_branch(branch_name)
+ within_element(:all_branches) do
+ within(".js-branch-#{branch_name}") do
+ accept_alert do
+ find_element(:remove_btn).click
+ end
+ end
+ end
+ end
+
+ def has_branch_title?(branch_title)
+ within_element(:all_branches) do
+ within(".item-title") do
+ has_text?(branch_title)
+ end
+ end
+ end
+
+ def has_branch_with_badge?(branch_name, badge)
+ within_element(:all_branches) do
+ within(".js-branch-#{branch_name} .badge") do
+ has_text?(badge)
+ end
+ end
+ end
+
+ def delete_merged_branches
+ accept_alert do
+ click_element(:delete_merged_branches)
+ end
+ end
+
+ def wait_for_texts_not_to_be_visible(texts)
+ text_not_visible = wait do
+ texts.all? do |text|
+ has_no_text?(text)
+ end
+ end
+ raise "Expected text(s) #{texts} not to be visible" unless text_not_visible
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 835e1ed00b5..eb3426b1ac0 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -4,11 +4,14 @@ module QA
module Page
module Project
class Menu < Page::Base
+ include SubMenus::Common
+ include SubMenus::Repository
+
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :settings_item
element :settings_link, 'link_to edit_project_path' # rubocop:disable QA/ElementWithPattern
- element :repository_link, "title: _('Repository')" # rubocop:disable QA/ElementWithPattern
element :link_pipelines
+ element :link_operations
element :link_members_settings
element :pipelines_settings_link, "title: _('CI / CD')" # rubocop:disable QA/ElementWithPattern
element :operations_kubernetes_link, "title: _('Kubernetes')" # rubocop:disable QA/ElementWithPattern
@@ -18,7 +21,6 @@ module QA
element :merge_requests_link, /link_to.*shortcuts-merge_requests/ # rubocop:disable QA/ElementWithPattern
element :merge_requests_link_text, "Merge Requests" # rubocop:disable QA/ElementWithPattern
element :top_level_items, '.sidebar-top-level-items' # rubocop:disable QA/ElementWithPattern
- element :operations_section, "class: 'shortcuts-operations'" # rubocop:disable QA/ElementWithPattern
element :activity_link, "title: _('Activity')" # rubocop:disable QA/ElementWithPattern
element :wiki_link_text, "Wiki" # rubocop:disable QA/ElementWithPattern
element :milestones_link
@@ -85,12 +87,6 @@ module QA
end
end
- def click_repository
- within_sidebar do
- click_link('Repository')
- end
- end
-
def click_repository_settings
hover_settings do
within_submenu do
@@ -129,6 +125,7 @@ module QA
def hover_issues
within_sidebar do
+ scroll_to_element(:issues_item)
find_element(:issues_item).hover
yield
@@ -137,7 +134,8 @@ module QA
def hover_operations
within_sidebar do
- find('.shortcuts-operations').hover
+ scroll_to_element(:link_operations)
+ find_element(:link_operations).hover
yield
end
@@ -145,20 +143,9 @@ module QA
def hover_settings
within_sidebar do
- find('.qa-settings-item').hover
-
- yield
- end
- end
-
- def within_sidebar
- page.within('.sidebar-top-level-items') do
- yield
- end
- end
+ scroll_to_element(:settings_item)
+ find_element(:settings_item).hover
- def within_submenu
- page.within('.fly-out-list') do
yield
end
end
diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb
new file mode 100644
index 00000000000..c94e1e85256
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/common.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Common
+ def within_sidebar
+ within('.sidebar-top-level-items') do
+ yield
+ end
+ end
+
+ def within_submenu
+ within('.fly-out-list') do
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb
new file mode 100644
index 00000000000..29eaa9a74de
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/repository.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Repository
+ def self.included(base)
+ base.class_eval do
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :project_menu_repo
+ element :branches_link
+ end
+ end
+ end
+
+ def click_repository
+ within_sidebar do
+ click_element(:project_menu_repo)
+ end
+ end
+
+ def click_repository_branches
+ hover_repository do
+ within_submenu do
+ click_element(:branches_link)
+ end
+ end
+ end
+
+ private
+
+ def hover_repository
+ within_sidebar do
+ find_element(:project_menu_repo).hover
+
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb
index c14d97ff7fb..f33aa0acef0 100644
--- a/qa/qa/resource/repository/push.rb
+++ b/qa/qa/resource/repository/push.rb
@@ -64,7 +64,7 @@ module QA
repository.configure_identity(username, email)
if new_branch
- repository.checkout_new_branch(branch_name)
+ repository.checkout(branch_name, new_branch: true)
else
repository.checkout(branch_name)
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 79b40223d84..23a2ace6a55 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -63,7 +63,7 @@ module QA
# - "https://user:pass@somehost.com:443/wd/hub"
# - "http://localhost:4444/wd/hub"
- return unless ENV['QA_REMOTE_GRID']
+ return if (ENV['QA_REMOTE_GRID'] || '').empty?
"#{remote_grid_protocol}://#{remote_grid_credentials}#{ENV['QA_REMOTE_GRID']}/wd/hub"
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
new file mode 100644
index 00000000000..0f0c627d79a
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Create, list, and delete branches via web' do
+ master_branch = 'master'
+ second_branch = 'second-branch'
+ third_branch = 'third-branch'
+ file_1_master = 'file.txt'
+ file_2_master = 'other-file.txt'
+ file_second_branch = 'file-2.txt'
+ file_third_branch = 'file-3.txt'
+ first_commit_message_of_master_branch = "Add #{file_1_master}"
+ second_commit_message_of_master_branch = "Add #{file_2_master}"
+ commit_message_of_second_branch = "Add #{file_second_branch}"
+ commit_message_of_third_branch = "Add #{file_third_branch}"
+
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ project = Resource::Project.fabricate! do |proj|
+ proj.name = 'project-qa-test'
+ proj.description = 'project for qa test'
+ end
+ project.visit!
+
+ Git::Repository.perform do |repository|
+ repository.uri = project.repository_http_location.uri
+ repository.use_default_credentials
+
+ repository.act do
+ clone
+ configure_identity('GitLab QA', 'root@gitlab.com')
+ commit_file(file_1_master, 'Test file content', first_commit_message_of_master_branch)
+ push_changes
+ checkout(second_branch, new_branch: true)
+ commit_file(file_second_branch, 'File 2 content', commit_message_of_second_branch)
+ push_changes(second_branch)
+ checkout(master_branch)
+ # This second commit on master is needed for the master branch to be ahead
+ # of the second branch, and when the second branch is merged to master it will
+ # show the 'merged' badge on it.
+ # Refer to the below issue note:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55524#note_126100848
+ commit_file(file_2_master, 'Other test file content', second_commit_message_of_master_branch)
+ push_changes
+ merge(second_branch)
+ push_changes
+ checkout(third_branch, new_branch: true)
+ commit_file(file_third_branch, 'File 3 content', commit_message_of_third_branch)
+ push_changes(third_branch)
+ end
+ end
+ Page::Project::Show.perform(&:wait_for_push)
+ end
+
+ it 'branches are correctly listed after CRUD operations' do
+ Page::Project::Menu.perform(&:click_repository_branches)
+
+ expect(page).to have_content(master_branch)
+ expect(page).to have_content(second_branch)
+ expect(page).to have_content(third_branch)
+ expect(page).to have_content("Merge branch 'second-branch'")
+ expect(page).to have_content(commit_message_of_second_branch)
+ expect(page).to have_content(commit_message_of_third_branch)
+
+ Page::Project::Branches::Show.perform do |branches|
+ expect(branches).to have_branch_with_badge(second_branch, 'merged')
+ end
+
+ Page::Project::Branches::Show.perform do |branches_view|
+ branches_view.delete_branch(third_branch)
+ end
+
+ expect(page).not_to have_content(third_branch)
+
+ Page::Project::Branches::Show.perform(&:delete_merged_branches)
+
+ expect(page).to have_content(
+ 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
+ )
+
+ page.refresh
+ Page::Project::Branches::Show.perform do |branches_view|
+ branches_view.wait_for_texts_not_to_be_visible([commit_message_of_second_branch])
+ expect(branches_view).not_to have_branch_title(second_branch)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/tools/revoke_all_personal_access_tokens.rb b/qa/qa/tools/revoke_all_personal_access_tokens.rb
new file mode 100644
index 00000000000..7484b633bf6
--- /dev/null
+++ b/qa/qa/tools/revoke_all_personal_access_tokens.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require_relative '../../qa'
+require 'net/protocol.rb'
+# This script revokes all personal access tokens with the name of 'api-test-token' on the host specified by GITLAB_ADDRESS
+# Required environment variables: GITLAB_USERNAME, GITLAB_PASSWORD and GITLAB_ADDRESS
+# Run `rake revoke_personal_access_tokens`
+
+module QA
+ module Tools
+ class RevokeAllPersonalAccessTokens
+ def run
+ do_run
+ rescue Net::ReadTimeout
+ STDOUT.puts 'Net::ReadTimeout during run. Trying again'
+ run
+ end
+
+ private
+
+ def do_run
+ raise ArgumentError, "Please provide GITLAB_USERNAME" unless ENV['GITLAB_USERNAME']
+ raise ArgumentError, "Please provide GITLAB_PASSWORD" unless ENV['GITLAB_PASSWORD']
+ raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
+
+ STDOUT.puts 'Running...'
+
+ Runtime::Browser.visit(ENV['GITLAB_ADDRESS'], Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Profile::Menu.perform(&:click_access_tokens)
+
+ token_name = 'api-test-token'
+
+ Page::Profile::PersonalAccessTokens.perform do |page|
+ while page.has_token_row_for_name?(token_name)
+ page.revoke_first_token_with_name(token_name)
+ print "\e[32m.\e[0m"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb
index c8b8aca51ab..1d37b1bd12d 100644
--- a/rubocop/cop/inject_enterprise_edition_module.rb
+++ b/rubocop/cop/inject_enterprise_edition_module.rb
@@ -11,9 +11,13 @@ module RuboCop
METHODS = Set.new(%i[include extend prepend]).freeze
- def_node_matcher :ee_const?, <<~PATTERN
- (const (const _ :EE) _)
- PATTERN
+ def ee_const?(node)
+ line = node.location.expression.source_line
+
+ # We use `match?` here instead of RuboCop's AST matching, as this makes
+ # it far easier to handle nested constants such as `EE::Foo::Bar::Baz`.
+ line.match?(/(\s|\()(::)?EE::/)
+ end
def on_send(node)
return unless METHODS.include?(node.children[1])
diff --git a/rubocop/spec_helpers.rb b/rubocop/spec_helpers.rb
index 9bf5f1e3b18..63c1b975a65 100644
--- a/rubocop/spec_helpers.rb
+++ b/rubocop/spec_helpers.rb
@@ -1,6 +1,7 @@
module RuboCop
module SpecHelpers
SPEC_HELPERS = %w[fast_spec_helper.rb rails_helper.rb spec_helper.rb].freeze
+ MIGRATION_SPEC_DIRECTORIES = ['spec/migrations', 'spec/lib/gitlab/background_migration'].freeze
# Returns true if the given node originated from the spec directory.
def in_spec?(node)
@@ -10,14 +11,18 @@ module RuboCop
path.start_with?(File.join(Dir.pwd, 'spec'), File.join(Dir.pwd, 'ee', 'spec'))
end
+ def migration_directories
+ @migration_directories ||= MIGRATION_SPEC_DIRECTORIES.map do |dir|
+ [File.join(Dir.pwd, dir), File.join(Dir.pwd, 'ee', dir)]
+ end.flatten
+ end
+
# Returns true if the given node originated from a migration spec.
def in_migration_spec?(node)
path = node.location.expression.source_buffer.name
in_spec?(node) &&
- path.start_with?(
- File.join(Dir.pwd, 'spec', 'migrations'),
- File.join(Dir.pwd, 'ee', 'spec', 'migrations'))
+ path.start_with?(*migration_directories)
end
end
end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 4e1dbff7b80..1ee6f502b8e 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -346,7 +346,6 @@ function wait_for_job_to_be_done() {
if [[ "${job_status}" == "failed" ]]; then
echo "The '${job_name}' failed."
- exit 1
elif [[ "${job_status}" == "manual" ]]; then
echo "The '${job_name}' is manual."
else
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 43f561f7a25..c290acb72aa 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -519,12 +519,14 @@ describe ApplicationController do
get :index
expect(response).to have_gitlab_http_status(404)
+ expect(response).to render_template('errors/not_found')
end
it 'renders a 403 when a message is passed to access denied' do
get :index, params: { message: 'None shall pass' }
expect(response).to have_gitlab_http_status(403)
+ expect(response).to render_template('errors/access_denied')
end
it 'renders a status passed to access denied' do
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
index 2556bc3ae50..8eac3d9a459 100644
--- a/spec/controllers/projects/badges_controller_spec.rb
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -22,7 +22,44 @@ describe Projects::BadgesController do
expect(response).to have_gitlab_http_status(:ok)
end
- def get_badge(badge)
- get badge, params: { namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref }, format: :svg
+ it 'renders the `flat` badge layout by default' do
+ get_badge(:coverage)
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+
+ context 'when style param is set to `flat`' do
+ it 'renders the `flat` badge layout' do
+ get_badge(:coverage, 'flat')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
+
+ context 'when style param is set to an invalid type' do
+ it 'renders the `flat` (default) badge layout' do
+ get_badge(:coverage, 'xxx')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
+
+ context 'when style param is set to `flat-square`' do
+ it 'renders the `flat-square` badge layout' do
+ get_badge(:coverage, 'flat-square')
+
+ expect(response).to render_template('projects/badges/badge_flat-square')
+ end
+ end
+
+ def get_badge(badge, style = nil)
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ ref: pipeline.ref,
+ style: style
+ }
+
+ get badge, params: params, format: :svg
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index e0b6105bb94..a2c3bb2919d 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1030,19 +1030,6 @@ describe Projects::IssuesController do
let(:project) { create(:project, :public) }
let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') }
- context 'feature disabled' do
- it 'returns 404' do
- sign_in(user)
- project.add_maintainer(user)
-
- stub_feature_flags(issues_import_csv: false)
-
- import_csv
-
- expect(response).to have_gitlab_http_status :not_found
- end
- end
-
context 'unauthorized' do
it 'returns 404 for guests' do
sign_out(:user)
@@ -1131,6 +1118,7 @@ describe Projects::IssuesController do
context 'when user is setting notes filters' do
let(:issuable) { issue }
+ let(:issuable_parent) { project }
let!(:discussion_note) { create(:discussion_note_on_issue, :system, noteable: issuable, project: project) }
it_behaves_like 'issuable notes filter'
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 4f4d3ca226f..4451fd227e8 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -78,6 +78,7 @@ describe Projects::MergeRequestsController do
context 'when user is setting notes filters' do
let(:issuable) { merge_request }
+ let(:issuable_parent) { project }
let!(:discussion_note) { create(:discussion_note_on_merge_request, :system, noteable: issuable, project: project) }
let!(:discussion_comment) { create(:discussion_note_on_merge_request, noteable: issuable, project: project) }
diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb
index 382c1b5d124..4b742a5d427 100644
--- a/spec/controllers/projects/pages_controller_spec.rb
+++ b/spec/controllers/projects/pages_controller_spec.rb
@@ -28,10 +28,10 @@ describe Projects::PagesController do
let(:group) { create(:group, :nested) }
let(:project) { create(:project, namespace: group) }
- it 'returns a 404 status code' do
+ it 'returns a 200 status code' do
get :show, params: request_params
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(200)
end
end
end
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index 2335b5118dd..ae257d769e8 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -5,7 +5,7 @@ FactoryBot.define do
transient do
attrs do
{
- title: 'Title',
+ title: 'Title.with.dot',
content: 'Content for wiki page',
format: 'markdown'
}
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index baa2b1d8af5..08c27354bd2 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -97,7 +97,7 @@ describe 'Issue Boards', :js do
expect(find('.board:nth-child(4)')).to have_selector('.board-card')
end
- it 'shows description tooltip on list title' do
+ it 'shows description tooltip on list title', :quarantine do
page.within('.board:nth-child(2)') do
expect(find('.board-title span.has-tooltip')[:title]).to eq('Test')
end
@@ -411,7 +411,7 @@ describe 'Issue Boards', :js do
wait_for_empty_boards((2..4))
end
- it 'filters by label with space after reload' do
+ it 'filters by label with space after reload', :quarantine do
set_filter("label", "\"#{accepting.title}")
click_filter_link(accepting.title)
submit_filter
@@ -477,7 +477,7 @@ describe 'Issue Boards', :js do
end
end
- it 'filters by multiple labels' do
+ it 'filters by multiple labels', :quarantine do
set_filter("label", testing.title)
click_filter_link(testing.title)
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 975b7944741..edca8f9df08 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -91,6 +91,7 @@ describe 'Dashboard Projects' do
visit dashboard_projects_path
expect(page).to have_content(project.name)
+ expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1)
end
it 'shows personal projects on personal projects tab', :js do
@@ -121,6 +122,8 @@ describe 'Dashboard Projects' do
expect(page).not_to have_content(project.name)
expect(page).to have_content(project2.name)
+ expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1)
+ expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1)
end
end
diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb
index ea61f9675bc..84636ae355c 100644
--- a/spec/features/merge_request/user_merges_immediately_spec.rb
+++ b/spec/features/merge_request/user_merges_immediately_spec.rb
@@ -25,6 +25,8 @@ describe 'Merge requests > User merges immediately', :js do
end
it 'enables merge immediately' do
+ wait_for_requests
+
page.within '.mr-widget-body' do
find('.dropdown-toggle').click
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index 0959f1b12f3..5188dc3625f 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -51,22 +51,52 @@ describe 'Merge request < User sees mini pipeline graph', :js do
first('.mini-pipeline-graph-dropdown-toggle')
end
- it 'expands when hovered' do
+ # Status icon button styles should update as described in
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/42769
+ it 'has unique styles for default, :hover, :active, and :focus states' do
find('.mini-pipeline-graph-dropdown-toggle')
- before_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();")
+ default_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');")
+ default_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');")
+ default_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');")
toggle.hover
find('.mini-pipeline-graph-dropdown-toggle')
- after_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();")
+ hover_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');")
+ hover_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');")
+ hover_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');")
- expect(before_width).to be < after_width
- end
+ page.driver.browser.action.click_and_hold(toggle.native).perform
- it 'shows dropdown caret when hovered' do
- toggle.hover
+ find('.mini-pipeline-graph-dropdown-toggle')
+ active_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');")
+ active_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');")
+ active_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');")
+
+ page.driver.browser.action.release(toggle.native)
+ .move_by(100, 100)
+ .perform
+
+ find('.mini-pipeline-graph-dropdown-toggle')
+ focus_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');")
+ focus_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');")
+ focus_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');")
+
+ expect(default_background_color).not_to eq(hover_background_color)
+ expect(hover_background_color).not_to eq(active_background_color)
+ expect(default_background_color).not_to eq(active_background_color)
+
+ expect(default_foreground_color).not_to eq(hover_foreground_color)
+ expect(hover_foreground_color).not_to eq(active_foreground_color)
+ expect(default_foreground_color).not_to eq(active_foreground_color)
+
+ expect(focus_background_color).to eq(hover_background_color)
+ expect(focus_foreground_color).to eq(hover_foreground_color)
- expect(toggle).to have_selector('.fa-caret-down')
+ expect(default_box_shadow).to eq('none')
+ expect(hover_box_shadow).to eq('none')
+ expect(active_box_shadow).not_to eq('none')
+ expect(focus_box_shadow).not_to eq('none')
end
it 'shows tooltip when hovered' do
diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
index 9ebbbaea911..5f630c9ffa4 100644
--- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
@@ -25,8 +25,8 @@ describe "User browses artifacts" do
page.within(".tree-table") do
expect(page).to have_no_content("..")
.and have_content("other_artifacts_0.1.2")
- .and have_content("ci_artifacts.txt")
- .and have_content("rails_sample.jpg")
+ .and have_content("ci_artifacts.txt 27 Bytes")
+ .and have_content("rails_sample.jpg 34.4 KB")
end
page.within(".build-header") do
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 4706c28bb3d..3192c9ffad4 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -477,10 +477,11 @@ describe 'Pipeline', :js do
end
context 'when accessing failed jobs page' do
- it 'fails to access the page' do
- subject
+ it 'renders a 404 page' do
+ requests = inspect_requests { subject }
- expect(page).to have_title('Access Denied')
+ expect(page).to have_title('Not Found')
+ expect(requests.first.status_code).to eq(404)
end
end
end
diff --git a/spec/fixtures/malicious.bundle b/spec/fixtures/malicious.bundle
new file mode 100644
index 00000000000..7ba47932906
--- /dev/null
+++ b/spec/fixtures/malicious.bundle
@@ -0,0 +1 @@
+gitdir: foo.git
diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
index 7237274eb43..53b9ac22fc0 100644
--- a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
@@ -1 +1,34 @@
// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import CompareVersionsDropdown from '~/diffs/components/compare_versions_dropdown.vue';
+import diffsMockData from '../mock_data/merge_request_diffs';
+
+describe('CompareVersionsDropdown', () => {
+ let wrapper;
+ const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
+
+ const factory = (options = {}) => {
+ const localVue = createLocalVue();
+
+ wrapper = shallowMount(CompareVersionsDropdown, { localVue, ...options });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render a correct base version link', () => {
+ factory({
+ propsData: {
+ baseVersionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ otherVersions: diffsMockData.slice(1),
+ targetBranch,
+ },
+ });
+
+ const links = wrapper.findAll('a');
+ const lastLink = links.wrappers[links.length - 1];
+
+ expect(lastLink.attributes('href')).toEqual(wrapper.props('baseVersionPath'));
+ });
+});
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 75c66e9ca82..50135b0cf86 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -100,6 +100,12 @@ describe('CompareVersions', () => {
});
});
+ describe('baseVersionPath', () => {
+ it('should be set correctly from mergeRequestDiff', () => {
+ expect(vm.baseVersionPath).toEqual(vm.mergeRequestDiff.base_version_path);
+ });
+ });
+
describe('isWhitespaceVisible', () => {
const originalHref = window.location.href;
diff --git a/spec/javascripts/diffs/mock_data/merge_request_diffs.js b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
index d72ad7818dd..4bbef146336 100644
--- a/spec/javascripts/diffs/mock_data/merge_request_diffs.js
+++ b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
@@ -1,42 +1,46 @@
export default [
{
- versionIndex: 4,
- createdAt: '2018-10-23T11:49:16.611Z',
- commitsCount: 4,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ version_index: 4,
+ created_at: '2018-10-23T11:49:16.611Z',
+ commits_count: 4,
latest: true,
- shortCommitSha: 'de7a8f7f',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
- comparePath:
+ short_commit_sha: 'de7a8f7f',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=de7a8f7f20c3ea2e0bef3ba01cfd41c21f6b4995',
},
{
- versionIndex: 3,
- createdAt: '2018-10-23T11:46:40.617Z',
- commitsCount: 3,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
+ version_index: 3,
+ created_at: '2018-10-23T11:46:40.617Z',
+ commits_count: 3,
latest: false,
- shortCommitSha: 'e78fc18f',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
- comparePath:
+ short_commit_sha: 'e78fc18f',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=e78fc18fa37acb2185c59ca94d4a964464feb50e',
},
{
- versionIndex: 2,
- createdAt: '2018-10-04T09:57:39.648Z',
- commitsCount: 2,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
+ version_index: 2,
+ created_at: '2018-10-04T09:57:39.648Z',
+ commits_count: 2,
latest: false,
- shortCommitSha: '48da7e7e',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
- comparePath:
+ short_commit_sha: '48da7e7e',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=48da7e7e9a99d41c852578bd9cb541ca4d864b3e',
},
{
- versionIndex: 1,
- createdAt: '2018-09-25T20:30:39.493Z',
- commitsCount: 1,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
+ version_index: 1,
+ created_at: '2018-09-25T20:30:39.493Z',
+ commits_count: 1,
latest: false,
- shortCommitSha: '47bac2ed',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
- comparePath:
+ short_commit_sha: '47bac2ed',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=47bac2ed972c5bee344c1cea159a22cd7f711dc0',
},
];
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index d8733941181..c595c38ef55 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -628,4 +628,26 @@ describe('DiffsStoreMutations', () => {
expect(file.parallel_diff_lines[1].right.hasForm).toBe(false);
});
});
+
+ describe('SET_TREE_DATA', () => {
+ it('sets treeEntries and tree in state', () => {
+ const state = {
+ treeEntries: {},
+ tree: [],
+ };
+
+ mutations[types.SET_TREE_DATA](state, {
+ treeEntries: { file: { name: 'index.js' } },
+ tree: ['tree'],
+ });
+
+ expect(state.treeEntries).toEqual({
+ file: {
+ name: 'index.js',
+ },
+ });
+
+ expect(state.tree).toEqual(['tree']);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 036b320b314..ea86844ddca 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -601,4 +601,123 @@ describe('DiffsStoreUtils', () => {
expect(utils.getDiffMode({})).toBe('replaced');
});
});
+
+ describe('getLowestSingleFolder', () => {
+ it('returns path and tree of lowest single folder tree', () => {
+ const folder = {
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ name: 'javascripts',
+ type: 'tree',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ },
+ ],
+ },
+ ],
+ };
+ const { path, treeAcc } = utils.getLowestSingleFolder(folder);
+
+ expect(path).toEqual('app/javascripts');
+ expect(treeAcc).toEqual([
+ {
+ type: 'blob',
+ name: 'index.js',
+ },
+ ]);
+ });
+
+ it('returns passed in folders path & tree when more than tree exists', () => {
+ const folder = {
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ name: 'spec',
+ type: 'blob',
+ tree: [],
+ },
+ ],
+ };
+ const { path, treeAcc } = utils.getLowestSingleFolder(folder);
+
+ expect(path).toEqual('app');
+ expect(treeAcc).toBeNull();
+ });
+ });
+
+ describe('flattenTree', () => {
+ it('returns flattened directory structure', () => {
+ const tree = [
+ {
+ type: 'tree',
+ name: 'app',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ tree: [],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'spec',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [],
+ },
+ {
+ type: 'blob',
+ name: 'index_spec.js',
+ tree: [],
+ },
+ ],
+ },
+ ];
+ const flattened = utils.flattenTree(tree);
+
+ expect(flattened).toEqual([
+ {
+ type: 'tree',
+ name: 'app/javascripts',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ tree: [],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'spec',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [],
+ },
+ {
+ type: 'blob',
+ name: 'index_spec.js',
+ tree: [],
+ },
+ ],
+ },
+ ]);
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
index 64aa7e29718..96bc3b0cc17 100644
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
@@ -6,6 +6,8 @@ import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link
const TEST_IMAGE_SIZE = 7;
const TEST_BREAKPOINT = 5;
+const TEST_EMPTY_MESSAGE = 'Lorem ipsum empty';
+const DEFAULT_EMPTY_MESSAGE = 'None';
const createUser = id => ({
id,
@@ -21,14 +23,19 @@ const createList = n =>
const localVue = createLocalVue();
describe('UserAvatarList', () => {
- let propsData;
+ let props;
let wrapper;
- const factory = options => {
+ const factory = (options = {}) => {
+ const propsData = {
+ ...props,
+ ...options.propsData,
+ };
+
wrapper = shallowMount(localVue.extend(UserAvatarList), {
+ ...options,
localVue,
propsData,
- ...options,
});
};
@@ -38,28 +45,47 @@ describe('UserAvatarList', () => {
};
beforeEach(() => {
- propsData = { imgSize: TEST_IMAGE_SIZE };
+ props = { imgSize: TEST_IMAGE_SIZE };
});
afterEach(() => {
wrapper.destroy();
});
+ describe('empty text', () => {
+ it('shows when items are empty', () => {
+ factory({ propsData: { items: [] } });
+
+ expect(wrapper.text()).toContain(DEFAULT_EMPTY_MESSAGE);
+ });
+
+ it('does not show when items are not empty', () => {
+ factory({ propsData: { items: createList(1) } });
+
+ expect(wrapper.text()).not.toContain(DEFAULT_EMPTY_MESSAGE);
+ });
+
+ it('can be set in props', () => {
+ factory({ propsData: { items: [], emptyText: TEST_EMPTY_MESSAGE } });
+
+ expect(wrapper.text()).toContain(TEST_EMPTY_MESSAGE);
+ });
+ });
+
describe('with no breakpoint', () => {
beforeEach(() => {
- propsData.breakpoint = 0;
+ props.breakpoint = 0;
});
it('renders avatars', () => {
const items = createList(20);
- propsData.items = items;
- factory();
+ factory({ propsData: { items } });
const links = wrapper.findAll(UserAvatarLink);
const linkProps = links.wrappers.map(x => x.props());
expect(linkProps).toEqual(
- propsData.items.map(x =>
+ items.map(x =>
jasmine.objectContaining({
linkHref: x.web_url,
imgSrc: x.avatar_url,
@@ -74,8 +100,8 @@ describe('UserAvatarList', () => {
describe('with breakpoint and length equal to breakpoint', () => {
beforeEach(() => {
- propsData.breakpoint = TEST_BREAKPOINT;
- propsData.items = createList(TEST_BREAKPOINT);
+ props.breakpoint = TEST_BREAKPOINT;
+ props.items = createList(TEST_BREAKPOINT);
});
it('renders all avatars if length is <= breakpoint', () => {
@@ -83,7 +109,7 @@ describe('UserAvatarList', () => {
const links = wrapper.findAll(UserAvatarLink);
- expect(links.length).toEqual(propsData.items.length);
+ expect(links.length).toEqual(props.items.length);
});
it('does not show button', () => {
@@ -95,8 +121,8 @@ describe('UserAvatarList', () => {
describe('with breakpoint and length greater than breakpoint', () => {
beforeEach(() => {
- propsData.breakpoint = TEST_BREAKPOINT;
- propsData.items = createList(TEST_BREAKPOINT + 1);
+ props.breakpoint = TEST_BREAKPOINT;
+ props.items = createList(TEST_BREAKPOINT + 1);
});
it('renders avatars up to breakpoint', () => {
@@ -116,7 +142,7 @@ describe('UserAvatarList', () => {
it('renders all avatars', () => {
const links = wrapper.findAll(UserAvatarLink);
- expect(links.length).toEqual(propsData.items.length);
+ expect(links.length).toEqual(props.items.length);
});
it('with collapse clicked, it renders avatars up to breakpoint', () => {
diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb
new file mode 100644
index 00000000000..2e50e4e2351
--- /dev/null
+++ b/spec/lib/banzai/filter/footnote_filter_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::FootnoteFilter do
+ include FilterSpecHelper
+
+ # first[^1] and second[^second]
+ # [^1]: one
+ # [^second]: two
+ let(:footnote) do
+ <<~EOF
+ <p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p>
+ <ol>
+ <li id="fn1">
+ <p>one <a href="#fnref1">↩</a></p>
+ </li>
+ <li id="fn2">
+ <p>two <a href="#fnref2">↩</a></p>
+ </li>
+ </ol>
+ EOF
+ end
+
+ let(:filtered_footnote) do
+ <<~EOF
+ <p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
+ <section class="footnotes"><ol>
+ <li id="fn1-#{identifier}">
+ <p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p>
+ </li>
+ <li id="fn2-#{identifier}">
+ <p>two <a href="#fnref2-#{identifier}" class="footnote-backref">↩</a></p>
+ </li>
+ </ol></section>
+ EOF
+ end
+
+ context 'when footnotes exist' do
+ let(:doc) { filter(footnote) }
+ let(:link_node) { doc.css('sup > a').first }
+ let(:identifier) { link_node[:id].delete_prefix('fnref1-') }
+
+ it 'properly adds the necessary ids and classes' do
+ expect(doc.to_html).to eq filtered_footnote
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 0b3c2390304..836af18e0b6 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -246,7 +246,7 @@ describe Banzai::Filter::SanitizationFilter do
'protocol-based JS injection: spaces and entities' => {
input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
- output: '<a href>foo</a>'
+ output: '<a href="">foo</a>'
},
'protocol whitespace' => {
@@ -300,5 +300,48 @@ describe Banzai::Filter::SanitizationFilter do
expect(act.to_html).to eq exp
end
+
+ describe 'footnotes' do
+ it 'allows correct footnote id property on links' do
+ exp = %q{<a href="#fn1" id="fnref1">foo/bar.md</a>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+
+ it 'allows correct footnote id property on li element' do
+ exp = %q{<ol><li id="fn1">footnote</li></ol>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+
+ it 'removes invalid id for footnote links' do
+ exp = %q{<a href="#fn1">link</a>}
+
+ %w[fnrefx test xfnref1].each do |id|
+ act = filter(%Q{<a href="#fn1" id="#{id}">link</a>})
+
+ expect(act.to_html).to eq exp
+ end
+ end
+
+ it 'removes invalid id for footnote li' do
+ exp = %q{<ol><li>footnote</li></ol>}
+
+ %w[fnx test xfn1].each do |id|
+ act = filter(%Q{<ol><li id="#{id}">footnote</li></ol>})
+
+ expect(act.to_html).to eq exp
+ end
+ end
+
+ it 'allows footnotes numbered higher than 9' do
+ exp = %q{<a href="#fn15" id="fnref15">link</a><ol><li id="fn15">footnote</li></ol>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+ end
end
end
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
index e9c7a2f352e..3634655c6a5 100644
--- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -25,4 +25,36 @@ describe Banzai::Pipeline::FullPipeline do
expect(result).to include(%{data-original='\"&gt;bad things'})
end
end
+
+ describe 'footnotes' do
+ let(:project) { create(:project, :public) }
+ let(:html) { described_class.to_html(footnote_markdown, project: project) }
+ let(:identifier) { html[/.*fnref1-(\d+).*/, 1] }
+ let(:footnote_markdown) do
+ <<~EOF
+ first[^1] and second[^second]
+ [^1]: one
+ [^second]: two
+ EOF
+ end
+
+ let(:filtered_footnote) do
+ <<~EOF
+ <p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
+
+ <section class="footnotes"><ol>
+ <li id="fn1-#{identifier}">
+ <p>one <a href="#fnref1-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
+ </li>
+ <li id="fn2-#{identifier}">
+ <p>two <a href="#fnref2-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
+ </li>
+ </ol></section>
+ EOF
+ end
+
+ it 'properly adds the necessary ids and classes' do
+ expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote
+ end
+ end
end
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 9d56c62ae57..630732614b2 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -182,4 +182,18 @@ describe Feature do
expect(described_class.disabled?(:enabled_feature_flag)).to be_falsey
end
end
+
+ describe Feature::Target do
+ describe '#targets' do
+ let(:project) { create(:project) }
+ let(:user_name) { project.owner.username }
+
+ subject { described_class.new(user: user_name, project: project.full_path) }
+
+ it 'returns all found targets' do
+ expect(subject.targets).to be_an(Array)
+ expect(subject.targets).to eq([project.owner, project])
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb
new file mode 100644
index 00000000000..7f2979e8e28
--- /dev/null
+++ b/spec/lib/gitlab/access/branch_protection_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Access::BranchProtection do
+ describe '#any?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true
+ Gitlab::Access::PROTECTION_FULL | true
+ end
+
+ with_them do
+ it { expect(described_class.new(level).any?).to eq(result) }
+ end
+ end
+
+ describe '#developer_can_push?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false
+ Gitlab::Access::PROTECTION_FULL | false
+ end
+
+ with_them do
+ it do
+ expect(described_class.new(level).developer_can_push?).to eq(result)
+ end
+ end
+ end
+
+ describe '#developer_can_merge?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true
+ Gitlab::Access::PROTECTION_FULL | false
+ end
+
+ with_them do
+ it do
+ expect(described_class.new(level).developer_can_merge?).to eq(result)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
index 53c071f0268..510a0074554 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
@@ -2,6 +2,7 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
let(:group) { create(:group, name: 'foo', path: 'foo') }
@@ -34,6 +35,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
let!(:project_hashed_storage_2) { create(:project, name: 'bar', path: 'bar', namespace: group, storage_version: 2) }
let!(:project_legacy_storage_3) { create(:project, name: 'baz', path: 'baz', namespace: group, storage_version: 0) }
let!(:project_legacy_storage_4) { create(:project, name: 'zoo', path: 'zoo', namespace: group, storage_version: nil) }
+ let!(:project_legacy_storage_5) { create(:project, name: 'test', path: 'test', namespace: group, storage_version: nil) }
describe '.on_hashed_storage' do
it 'finds projects with repository on hashed storage' do
@@ -47,7 +49,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
it 'finds projects with repository on legacy storage' do
projects = described_class.on_legacy_storage.pluck(:id)
- expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id])
+ expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id, project_legacy_storage_5.id])
end
end
@@ -58,7 +60,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
projects = described_class.without_project_repository.pluck(:id)
- expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id)
+ expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id, project_legacy_storage_5.id)
end
end
@@ -78,17 +80,27 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
expect(project.disk_path).to eq(project_legacy_storage_3.disk_path)
end
+ it 'returns the correct disk_path using the route entry' do
+ project_legacy_storage_5.route.update(path: 'zoo/new-test')
+ project = described_class.find(project_legacy_storage_5.id)
+
+ expect(project.disk_path).to eq('zoo/new-test')
+ end
+
it 'raises OrphanedNamespaceError when any parent namespace does not exist' do
subgroup = create(:group, parent: group)
project_orphaned_namespace = create(:project, name: 'baz', path: 'baz', namespace: subgroup, storage_version: nil)
subgroup.update_column(:parent_id, Namespace.maximum(:id).succ)
project = described_class.find(project_orphaned_namespace.id)
+ project.route.destroy
+ subgroup.route.destroy
- expect { project.disk_path }
+ expect { project.reload.disk_path }
.to raise_error(Gitlab::BackgroundMigration::BackfillProjectRepositories::OrphanedNamespaceError)
end
end
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
index f92acf61682..f974dc8fda2 100644
--- a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
+++ b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration, schema: 20171005130944 do
context 'when GpgKey exists' do
- let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) }
+ let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
before do
GpgKeySubkey.destroy_all # rubocop: disable DestroyAll
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 1969aed51da..27281333348 100644
--- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do
describe '#perform' do
context 'when diff files can be deleted' do
@@ -71,3 +72,4 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index 5dce3fcbcb6..bc71a90605a 100644
--- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do
include GitHelpers
@@ -324,3 +325,4 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
index 5432d270555..188969951a6 100644
--- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event, :migration, schema: 20170608152748 do
describe '#commit_title' do
it 'returns nil when there are no commits' do
@@ -429,3 +430,4 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migrati
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
index 021e1d14b18..ea8bdd48e72 100644
--- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete do
let(:migration) { described_class.new }
@@ -17,3 +18,4 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete d
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
index 2e77e80ee46..593486fc56c 100644
--- a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do
let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'move_snippet_files_test') }
let(:old_uploads_dir) { File.join('uploads', 'system', 'personal_snippet') }
@@ -70,3 +71,4 @@ describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do
"[an upload](#{path})"
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
index 4f1b01eed41..8e3cb36d313 100644
--- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
@@ -2,6 +2,7 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :migration, schema: 20181022173835 do
let(:migration) { described_class.new }
let(:clusters) { create_list(:cluster, 10, :project, :provided_by_gcp) }
@@ -95,3 +96,4 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
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 6ab126ad39a..3e009fed0f1 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
@@ -2,6 +2,7 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration, schema: 20180916011959 do
let(:migration) { described_class.new }
@@ -65,3 +66,4 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration
it_behaves_like 'no changes'
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
index e99257e3481..ff1bd9f7850 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
@@ -1,5 +1,6 @@
require 'rails_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do
# commits_count attribute is added in a next migration
before do
@@ -128,3 +129,4 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData,
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/git/bundle_file_spec.rb b/spec/lib/gitlab/git/bundle_file_spec.rb
new file mode 100644
index 00000000000..ff7c981dadd
--- /dev/null
+++ b/spec/lib/gitlab/git/bundle_file_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::Git::BundleFile do
+ describe '.check!' do
+ let(:valid_bundle) { Tempfile.new }
+ let(:valid_bundle_path) { valid_bundle.path }
+ let(:invalid_bundle_path) { Rails.root.join('spec/fixtures/malicious.bundle') }
+
+ after do
+ valid_bundle.close!
+ end
+
+ it 'returns nil for a valid bundle' do
+ valid_bundle.write("# v2 git bundle\nfoo bar baz\n")
+ valid_bundle.close
+
+ expect(described_class.check!(valid_bundle_path)).to be_nil
+ end
+
+ it 'raises an exception for an invalid bundle' do
+ expect do
+ described_class.check!(invalid_bundle_path)
+ end.to raise_error(described_class::InvalidBundleError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 852ee9c96af..a19e3e84f83 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1753,22 +1753,23 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#create_from_bundle' do
- let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:valid_bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:malicious_bundle_path) { Rails.root.join('spec/fixtures/malicious.bundle') }
let(:project) { create(:project) }
let(:imported_repo) { project.repository.raw }
before do
- expect(repository.bundle_to_disk(bundle_path)).to be_truthy
+ expect(repository.bundle_to_disk(valid_bundle_path)).to be_truthy
end
after do
- FileUtils.rm_rf(bundle_path)
+ FileUtils.rm_rf(valid_bundle_path)
end
it 'creates a repo from a bundle file' do
expect(imported_repo).not_to exist
- result = imported_repo.create_from_bundle(bundle_path)
+ result = imported_repo.create_from_bundle(valid_bundle_path)
expect(result).to be_truthy
expect(imported_repo).to exist
@@ -1776,11 +1777,17 @@ describe Gitlab::Git::Repository, :seed_helper do
end
it 'creates a symlink to the global hooks dir' do
- imported_repo.create_from_bundle(bundle_path)
+ imported_repo.create_from_bundle(valid_bundle_path)
hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
end
+
+ it 'raises an error if the bundle is an attempted malicious payload' do
+ expect do
+ imported_repo.create_from_bundle(malicious_bundle_path)
+ end.to raise_error(::Gitlab::Git::BundleFile::InvalidBundleError)
+ end
end
describe '#checksum' do
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index bdb1f34d2f6..24d49a049b6 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -101,16 +101,36 @@ describe Gitlab::Middleware::ReadOnly do
expect(subject).not_to disallow_request
end
- it 'expects requests to sidekiq admin to be allowed' do
- response = request.post('/admin/sidekiq')
+ context 'sidekiq admin requests' do
+ where(:mounted_at) do
+ [
+ '',
+ '/',
+ '/gitlab',
+ '/gitlab/',
+ '/gitlab/gitlab',
+ '/gitlab/gitlab/'
+ ]
+ end
- expect(response).not_to be_redirect
- expect(subject).not_to disallow_request
+ with_them do
+ before do
+ stub_config_setting(relative_url_root: mounted_at)
+ end
- response = request.get('/admin/sidekiq')
+ it 'allows requests' do
+ path = File.join(mounted_at, 'admin/sidekiq')
+ response = request.post(path)
- expect(response).not_to be_redirect
- expect(subject).not_to disallow_request
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+
+ response = request.get(path)
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+ end
end
where(:description, :path) do
diff --git a/spec/lib/gitlab/tracing/factory_spec.rb b/spec/lib/gitlab/tracing/factory_spec.rb
new file mode 100644
index 00000000000..945490f0988
--- /dev/null
+++ b/spec/lib/gitlab/tracing/factory_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::Factory do
+ describe '.create_tracer' do
+ let(:service_name) { 'rspec' }
+
+ context "when tracing is not configured" do
+ it 'ignores null connection strings' do
+ expect(described_class.create_tracer(service_name, nil)).to be_nil
+ end
+
+ it 'ignores empty connection strings' do
+ expect(described_class.create_tracer(service_name, '')).to be_nil
+ end
+
+ it 'ignores unknown implementations' do
+ expect(described_class.create_tracer(service_name, 'opentracing://invalid_driver')).to be_nil
+ end
+
+ it 'ignores invalid connection strings' do
+ expect(described_class.create_tracer(service_name, 'open?tracing')).to be_nil
+ end
+ end
+
+ context "when tracing is configured with jaeger" do
+ let(:mock_tracer) { double('tracer') }
+
+ it 'processes default connections' do
+ expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, {}).and_return(mock_tracer)
+
+ expect(described_class.create_tracer(service_name, 'opentracing://jaeger')).to be(mock_tracer)
+ end
+
+ it 'processes connections with parameters' do
+ expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, { a: '1', b: '2', c: '3' }).and_return(mock_tracer)
+
+ expect(described_class.create_tracer(service_name, 'opentracing://jaeger?a=1&b=2&c=3')).to be(mock_tracer)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/jaeger_factory_spec.rb b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
new file mode 100644
index 00000000000..3d6a007cfd9
--- /dev/null
+++ b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::JaegerFactory do
+ describe '.create_tracer' do
+ let(:service_name) { 'rspec' }
+
+ shared_examples_for 'a jaeger tracer' do
+ it 'responds to active_span methods' do
+ expect(tracer).to respond_to(:active_span)
+ end
+
+ it 'yields control' do
+ expect { |b| tracer.start_active_span('operation_name', &b) }.to yield_control
+ end
+ end
+
+ context 'processes default connections' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, {}) }
+ end
+ end
+
+ context 'handles debug options' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { debug: "1" }) }
+ end
+ end
+
+ context 'handles const sampler' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { sampler: "const", sampler_param: "1" }) }
+ end
+ end
+
+ context 'handles probabilistic sampler' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { sampler: "probabilistic", sampler_param: "0.5" }) }
+ end
+ end
+
+ context 'handles http_endpoint configurations' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { http_endpoint: "http://localhost:1234" }) }
+ end
+ end
+
+ context 'handles udp_endpoint configurations' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { udp_endpoint: "localhost:4321" }) }
+ end
+ end
+
+ context 'ignores invalid parameters' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { invalid: "true" }) }
+ end
+ end
+
+ context 'accepts the debug parameter when strict_parser is set' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { debug: "1", strict_parsing: "1" }) }
+ end
+ end
+
+ it 'rejects invalid parameters when strict_parser is set' do
+ expect { described_class.create_tracer(service_name, { invalid: "true", strict_parsing: "1" }) }.to raise_error(StandardError)
+ end
+ end
+end
diff --git a/spec/migrations/add_foreign_keys_to_todos_spec.rb b/spec/migrations/add_foreign_keys_to_todos_spec.rb
index bf2fa5c0f56..efd87173b9c 100644
--- a/spec/migrations/add_foreign_keys_to_todos_spec.rb
+++ b/spec/migrations/add_foreign_keys_to_todos_spec.rb
@@ -3,9 +3,11 @@ require Rails.root.join('db', 'migrate', '20180201110056_add_foreign_keys_to_tod
describe AddForeignKeysToTodos, :migration do
let(:todos) { table(:todos) }
+ let(:users) { table(:users) }
+ let(:projects) { table(:projects) }
- let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:project) { projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1) }
+ let(:user) { users.create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) }
context 'add foreign key on user_id' do
let!(:todo_with_user) { create_todo(user_id: user.id) }
diff --git a/spec/migrations/cleanup_legacy_artifact_migration_spec.rb b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
new file mode 100644
index 00000000000..dc269d32e5a
--- /dev/null
+++ b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190104182041_cleanup_legacy_artifact_migration.rb')
+
+describe CleanupLegacyArtifactMigration, :migration, :sidekiq, :redis do
+ let(:migration) { spy('migration') }
+
+ context 'when still legacy artifacts exist' do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:jobs) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id) }
+ let(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a') }
+ let(:archive_file_type) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::ARCHIVE_FILE_TYPE }
+ let(:metadata_file_type) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::METADATA_FILE_TYPE }
+ let(:local_store) { ::ObjectStorage::Store::LOCAL }
+ let(:remote_store) { ::ObjectStorage::Store::REMOTE }
+ let(:legacy_location) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::LEGACY_PATH_FILE_LOCATION }
+
+ before do
+ jobs.create!(id: 1, commit_id: pipeline.id, project_id: project.id, status: :success, artifacts_file: 'archive.zip')
+ jobs.create!(id: 2, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 3, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_file: 'archive.zip', artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 4, commit_id: pipeline.id, project_id: project.id, status: :running)
+ jobs.create!(id: 5, commit_id: pipeline.id, project_id: project.id, status: :success, artifacts_file: 'archive.zip', artifacts_file_store: remote_store, artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 6, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_file: 'archive.zip', artifacts_metadata: 'metadata.gz')
+ end
+
+ it 'steals sidekiq jobs from MigrateLegacyArtifacts background migration' do
+ expect(Gitlab::BackgroundMigration).to receive(:steal).with('MigrateLegacyArtifacts')
+
+ migrate!
+ end
+
+ it 'migrates legacy artifacts to ci_job_artifacts table' do
+ migrate!
+
+ expect(job_artifacts.order(:job_id, :file_type).pluck('project_id, job_id, file_type, file_store, size, expire_at, file, file_sha256, file_location'))
+ .to eq([[project.id, 1, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 3, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 3, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location],
+ [project.id, 5, archive_file_type, remote_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 5, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location],
+ [project.id, 6, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 6, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location]])
+ end
+ end
+end
diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
index b5980cb9ddb..651341906c2 100644
--- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespaceless_pending_delete_projects.rb')
describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222043024 do
+ let(:projects) { table(:projects) }
+
before do
# Stub after_save callbacks that will fail when Project has no namespace
allow_any_instance_of(Project).to receive(:ensure_storage_path_exists).and_return(nil)
@@ -10,9 +12,9 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
describe '#up' do
it 'only cleans up pending delete projects' do
- create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- project = build(:project, pending_delete: true, namespace_id: nil) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true)
+ project = Project.new(pending_delete: true, namespace_id: nil)
project.save(validate: false)
expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -21,8 +23,8 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
end
it 'does nothing when no pending delete projects without namespace found' do
- create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true)
expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 1afc2436bb5..60d89313f07 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -3028,6 +3028,24 @@ describe Ci::Build do
subject.drop!
end
end
+
+ context 'when associated deployment failed to update its status' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, deployable: build) }
+
+ before do
+ allow_any_instance_of(Deployment)
+ .to receive(:drop!).and_raise('Unexpected error')
+ end
+
+ it 'can drop the build' do
+ expect(Gitlab::Sentry).to receive(:track_exception)
+
+ expect { build.drop! }.not_to raise_error
+
+ expect(build).to be_failed
+ end
+ end
end
describe '.matches_tag_ids' do
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index de6b844023a..e50ba67c493 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -90,6 +90,24 @@ describe Clusters::Applications::Prometheus do
expect(application).not_to be_ready
end
+
+ it 'returns true when updating' do
+ application = build(:clusters_applications_prometheus, :updating, cluster: cluster)
+
+ expect(application).to be_ready
+ end
+
+ it 'returns true when updated' do
+ application = build(:clusters_applications_prometheus, :updated, cluster: cluster)
+
+ expect(application).to be_ready
+ end
+
+ it 'returns true when errored' do
+ application = build(:clusters_applications_prometheus, :update_errored, cluster: cluster)
+
+ expect(application).to be_ready
+ end
end
describe '#prometheus_client' do
@@ -197,6 +215,46 @@ describe Clusters::Applications::Prometheus do
end
end
+ describe '#upgrade_command' do
+ let(:prometheus) { build(:clusters_applications_prometheus) }
+ let(:values) { prometheus.values }
+
+ it 'returns an instance of Gitlab::Kubernetes::Helm::GetCommand' do
+ expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::UpgradeCommand)
+ end
+
+ it 'should be initialized with 3 arguments' do
+ command = prometheus.upgrade_command(values)
+
+ expect(command.name).to eq('prometheus')
+ expect(command.chart).to eq('stable/prometheus')
+ expect(command.version).to eq('6.7.3')
+ expect(command.files).to eq(prometheus.files)
+ end
+ end
+
+ describe '#update_in_progress?' do
+ context 'when app is updating' do
+ it 'returns true' do
+ cluster = create(:cluster)
+ prometheus_app = build(:clusters_applications_prometheus, :updating, cluster: cluster)
+
+ expect(prometheus_app.update_in_progress?).to be true
+ end
+ end
+ end
+
+ describe '#update_errored?' do
+ context 'when app errored' do
+ it 'returns true' do
+ cluster = create(:cluster)
+ prometheus_app = build(:clusters_applications_prometheus, :update_errored, cluster: cluster)
+
+ expect(prometheus_app.update_errored?).to be true
+ end
+ end
+ end
+
describe '#files' do
let(:application) { create(:clusters_applications_prometheus) }
let(:values) { subject[:'values.yaml'] }
@@ -211,4 +269,43 @@ describe Clusters::Applications::Prometheus do
expect(values).to include('serverFiles')
end
end
+
+ describe '#files_with_replaced_values' do
+ let(:application) { build(:clusters_applications_prometheus) }
+ let(:files) { application.files }
+
+ subject { application.files_with_replaced_values({ hello: :world }) }
+
+ it 'does not modify #files' do
+ expect(subject[:'values.yaml']).not_to eq(files)
+ expect(files[:'values.yaml']).to eq(application.values)
+ end
+
+ it 'returns values.yaml with replaced values' do
+ expect(subject[:'values.yaml']).to eq({ hello: :world })
+ end
+
+ it 'should include cert files' do
+ expect(subject[:'ca.pem']).to be_present
+ expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert)
+
+ expect(subject[:'cert.pem']).to be_present
+ expect(subject[:'key.pem']).to be_present
+
+ cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem'])
+ expect(cert.not_after).to be < 60.minutes.from_now
+ end
+
+ context 'when the helm application does not have a ca_cert' do
+ before do
+ application.cluster.application_helm.ca_cert = nil
+ end
+
+ it 'should not include cert files' do
+ expect(subject[:'ca.pem']).not_to be_present
+ expect(subject[:'cert.pem']).not_to be_present
+ expect(subject[:'key.pem']).not_to be_present
+ end
+ end
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 015db4d4e96..2e436f2cc8a 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -286,8 +286,8 @@ describe Milestone do
end
context 'relations as params' do
- let(:projects) { Project.where(id: project.id) }
- let(:groups) { Group.where(id: group.id) }
+ let(:projects) { Project.where(id: project.id).select(:id) }
+ let(:groups) { Group.where(id: group.id).select(:id) }
it_behaves_like 'filters by projects and groups'
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 397b4d7c61f..7a8dc59039e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -209,9 +209,14 @@ describe Project do
it 'does not allow new projects beyond user limits' do
project2 = build(:project)
- allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
+
+ allow(project2)
+ .to receive(:creator)
+ .and_return(
+ double(can_create_project?: false, projects_limit: 0).as_null_object
+ )
+
expect(project2).not_to be_valid
- expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
end
describe 'wiki path conflict' do
@@ -3087,7 +3092,7 @@ describe Project do
context 'when the project is in a subgroup' do
let(:group) { create(:group, :nested) }
- it { is_expected.to be(false) }
+ it { is_expected.to be(true) }
end
end
@@ -4431,6 +4436,75 @@ describe Project do
end
end
+ describe '#check_personal_projects_limit' do
+ context 'when creating a project for a group' do
+ it 'does nothing' do
+ creator = build(:user)
+ project = build(:project, namespace: build(:group), creator: creator)
+
+ allow(creator)
+ .to receive(:can_create_project?)
+ .and_return(false)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors).to be_empty
+ end
+ end
+
+ context 'when the user is not allowed to create a personal project' do
+ let(:user) { build(:user) }
+ let(:project) { build(:project, creator: user) }
+
+ before do
+ allow(user)
+ .to receive(:can_create_project?)
+ .and_return(false)
+ end
+
+ context 'when the project limit is zero' do
+ it 'adds a validation error' do
+ allow(user)
+ .to receive(:projects_limit)
+ .and_return(0)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors[:limit_reached].first)
+ .to match(/Personal project creation is not allowed/)
+ end
+ end
+
+ context 'when the project limit is greater than zero' do
+ it 'adds a validation error' do
+ allow(user)
+ .to receive(:projects_limit)
+ .and_return(5)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors[:limit_reached].first)
+ .to match(/Your project limit is 5 projects/)
+ end
+ end
+ end
+
+ context 'when the user is allowed to create personal projects' do
+ it 'does nothing' do
+ user = build(:user)
+ project = build(:project, creator: user)
+
+ allow(user)
+ .to receive(:can_create_project?)
+ .and_return(true)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors).to be_empty
+ end
+ end
+ end
+
def rugged_config
rugged_repo(project.repository).config
end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 224bc9ed935..c06e9a08ab4 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -303,6 +303,25 @@ describe RemoteMirror, :mailer do
end
end
+ context '#url=' do
+ let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
+
+ it 'resets all the columns when URL changes' do
+ remote_mirror.update(last_error: Time.now,
+ last_update_at: Time.now,
+ last_successful_update_at: Time.now,
+ update_status: 'started',
+ error_notification_sent: true)
+
+ expect { remote_mirror.update_attribute(:url, 'http://new.example.com') }
+ .to change { remote_mirror.last_error }.to(nil)
+ .and change { remote_mirror.last_update_at }.to(nil)
+ .and change { remote_mirror.last_successful_update_at }.to(nil)
+ .and change { remote_mirror.update_status }.to('finished')
+ .and change { remote_mirror.error_notification_sent }.to(false)
+ end
+ end
+
context '#updated_since?' do
let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
let(:timestamp) { Time.now - 5.minutes }
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 7d3eff7d32d..22a9e36ca31 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -129,6 +129,40 @@ describe API::Features do
end
end
+ context 'when enabling for a project by path' do
+ context 'when the project exists' do
+ let!(:project) { create(:project) }
+
+ it 'sets the feature gate' do
+ post api("/features/#{feature_name}", admin), params: { value: 'true', project: project.full_path }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'actors', 'value' => ["Project:#{project.id}"] }
+ ])
+ end
+ end
+
+ context 'when the project does not exist' do
+ it 'sets no new values' do
+ post api("/features/#{feature_name}", admin), params: { value: 'true', project: 'mep/to/the/mep/mep' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq(
+ "name" => "my_feature",
+ "state" => "off",
+ "gates" => [
+ { "key" => "boolean", "value" => false }
+ ]
+ )
+ end
+ end
+ end
+
it 'creates a feature with the given percentage if passed an integer' do
post api("/features/#{feature_name}", admin), params: { value: '50' }
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
new file mode 100644
index 00000000000..aceff9b4aa6
--- /dev/null
+++ b/spec/requests/api/import_github_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe API::ImportGithub do
+ include ApiHelpers
+
+ let(:token) { "asdasd12345" }
+ let(:provider) { :github }
+ let(:access_params) { { github_access_token: token } }
+
+ describe "POST /import/github" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:provider_username) { user.username }
+ let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:provider_repo) do
+ OpenStruct.new(
+ name: 'vim',
+ full_name: "#{provider_username}/vim",
+ owner: OpenStruct.new(login: provider_username)
+ )
+ end
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repo: provider_repo).as_null_object)
+ end
+ end
+
+ it 'returns 201 response when the project is imported successfully' do
+ allow(Gitlab::LegacyGithubImport::ProjectCreator)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .and_return(double(execute: project))
+
+ post api("/import/github", user), params: {
+ target_namespace: user.namespace_path,
+ personal_access_token: token,
+ repo_id: 1234
+ }
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq(project.name)
+ end
+
+ it 'returns 422 response when user can not create projects in the chosen namespace' do
+ other_namespace = create(:group, name: 'other_namespace')
+
+ post api("/import/github", user), params: {
+ target_namespace: other_namespace.name,
+ personal_access_token: token,
+ repo_id: 1234
+ }
+
+ expect(response).to have_gitlab_http_status(422)
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 0c48c796ceb..7248908b494 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -948,6 +948,7 @@ describe API::Projects do
expect(json_response['shared_with_groups'].length).to eq(1)
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
+ expect(json_response['shared_with_groups'][0]['group_full_path']).to eq(group.full_path)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]['expires_at']).to be_nil
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
@@ -967,6 +968,7 @@ describe API::Projects do
expect(json_response['shared_with_groups'].length).to eq(1)
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
+ expect(json_response['shared_with_groups'][0]['group_full_path']).to eq(group.full_path)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]['expires_at']).to eq(expires_at.to_s)
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index cfbda63bb30..45fb1562e84 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -63,7 +63,8 @@ describe API::Settings, 'Settings' do
terms: 'Hello world!',
performance_bar_allowed_group_path: group.full_path,
instance_statistics_visibility_private: true,
- diff_max_patch_bytes: 150_000
+ diff_max_patch_bytes: 150_000,
+ default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE
}
expect(response).to have_gitlab_http_status(200)
@@ -88,6 +89,7 @@ describe API::Settings, 'Settings' do
expect(json_response['performance_bar_allowed_group_id']).to eq(group.id)
expect(json_response['instance_statistics_visibility_private']).to be(true)
expect(json_response['diff_max_patch_bytes']).to eq(150_000)
+ expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
end
end
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index f5092e8e2b5..6109829aad1 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -22,7 +22,7 @@ describe API::Wikis do
context 'when wiki has pages' do
let!(:pages) do
[create(:wiki_page, wiki: project_wiki, attrs: { title: 'page1', content: 'content of page1' }),
- create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2', content: 'content of page2' })]
+ create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2.with.dot', content: 'content of page2' })]
end
it 'returns the list of wiki pages without content' do
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index 08ffc3c3a53..0ff777388e5 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -19,6 +19,41 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
SOURCE
end
+ it 'does not flag the use of `prepend EEFoo` in the middle of a file' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ prepend EEFoo
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend EE::Foo::Bar` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend EE::Foo::Bar
+ ^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend(EE::Foo::Bar)` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend(EE::Foo::Bar)
+ ^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend EE::Foo::Bar::Baz` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend EE::Foo::Bar::Baz
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
it 'flags the use of `prepend ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
diff --git a/spec/services/projects/protect_default_branch_service_spec.rb b/spec/services/projects/protect_default_branch_service_spec.rb
new file mode 100644
index 00000000000..c145b2c06c6
--- /dev/null
+++ b/spec/services/projects/protect_default_branch_service_spec.rb
@@ -0,0 +1,242 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::ProtectDefaultBranchService do
+ let(:service) { described_class.new(project) }
+ let(:project) { instance_spy(Project) }
+
+ describe '#execute' do
+ before do
+ allow(service)
+ .to receive(:protect_default_branch)
+ end
+
+ context 'without a default branch' do
+ it 'does nothing' do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return(nil)
+
+ service.execute
+
+ expect(service)
+ .not_to have_received(:protect_default_branch)
+ end
+ end
+
+ context 'with a default branch' do
+ it 'protects the default branch' do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ service.execute
+
+ expect(service)
+ .to have_received(:protect_default_branch)
+ end
+ end
+ end
+
+ describe '#protect_default_branch' do
+ before do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ allow(project)
+ .to receive(:change_head)
+ .with('master')
+
+ allow(service)
+ .to receive(:create_protected_branch)
+ end
+
+ context 'when branch protection is needed' do
+ before do
+ allow(service)
+ .to receive(:protect_branch?)
+ .and_return(true)
+
+ allow(service)
+ .to receive(:create_protected_branch)
+ end
+
+ it 'changes the HEAD of the project' do
+ service.protect_default_branch
+
+ expect(project)
+ .to have_received(:change_head)
+ end
+
+ it 'protects the default branch' do
+ service.protect_default_branch
+
+ expect(service)
+ .to have_received(:create_protected_branch)
+ end
+ end
+
+ context 'when branch protection is not needed' do
+ before do
+ allow(service)
+ .to receive(:protect_branch?)
+ .and_return(false)
+ end
+
+ it 'changes the HEAD of the project' do
+ service.protect_default_branch
+
+ expect(project)
+ .to have_received(:change_head)
+ end
+
+ it 'does not protect the default branch' do
+ service.protect_default_branch
+
+ expect(service)
+ .not_to have_received(:create_protected_branch)
+ end
+ end
+ end
+
+ describe '#create_protected_branch' do
+ it 'creates the protected branch' do
+ creator = instance_spy(User)
+ create_service = instance_spy(ProtectedBranches::CreateService)
+ access_level = Gitlab::Access::DEVELOPER
+ params = {
+ name: 'master',
+ push_access_levels_attributes: [{ access_level: access_level }],
+ merge_access_levels_attributes: [{ access_level: access_level }]
+ }
+
+ allow(project)
+ .to receive(:creator)
+ .and_return(creator)
+
+ allow(ProtectedBranches::CreateService)
+ .to receive(:new)
+ .with(project, creator, params)
+ .and_return(create_service)
+
+ allow(service)
+ .to receive(:push_access_level)
+ .and_return(access_level)
+
+ allow(service)
+ .to receive(:merge_access_level)
+ .and_return(access_level)
+
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ allow(create_service)
+ .to receive(:execute)
+ .with(skip_authorization: true)
+
+ service.create_protected_branch
+
+ expect(create_service)
+ .to have_received(:execute)
+ end
+ end
+
+ describe '#protect_branch?' do
+ context 'when default branch protection is disabled' do
+ it 'returns false' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_NONE)
+
+ expect(service.protect_branch?).to eq(false)
+ end
+ end
+
+ context 'when default branch protection is enabled' do
+ before do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+ end
+
+ it 'returns false if the branch is already protected' do
+ allow(ProtectedBranch)
+ .to receive(:protected?)
+ .with(project, 'master')
+ .and_return(true)
+
+ expect(service.protect_branch?).to eq(false)
+ end
+
+ it 'returns true if the branch is not yet protected' do
+ allow(ProtectedBranch)
+ .to receive(:protected?)
+ .with(project, 'master')
+ .and_return(false)
+
+ expect(service.protect_branch?).to eq(true)
+ end
+ end
+ end
+
+ describe '#default_branch' do
+ it 'returns the default branch of the project' do
+ allow(project)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ expect(service.default_branch).to eq('master')
+ end
+ end
+
+ describe '#push_access_level' do
+ context 'when developers can push' do
+ it 'returns the DEVELOPER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(service.push_access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when developers can not push' do
+ it 'returns the MAINTAINER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(service.push_access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+ end
+
+ describe '#merge_access_level' do
+ context 'when developers can merge' do
+ it 'returns the DEVELOPER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(service.merge_access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when developers can not merge' do
+ it 'returns the MAINTAINER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(service.merge_access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+ end
+end
diff --git a/spec/sidekiq/cron/job_gem_dependency_spec.rb b/spec/sidekiq/cron/job_gem_dependency_spec.rb
index 2e30cf025b0..2e7de75fd08 100644
--- a/spec/sidekiq/cron/job_gem_dependency_spec.rb
+++ b/spec/sidekiq/cron/job_gem_dependency_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Sidekiq::Cron::Job do
describe 'cron jobs' do
- context 'when rufus-scheduler depends on ZoTime or EoTime' do
+ context 'when Fugit depends on ZoTime or EoTime' do
before do
described_class
.create(name: 'TestCronWorker',
@@ -10,7 +10,7 @@ describe Sidekiq::Cron::Job do
class: Settings.cron_jobs[:pipeline_schedule_worker]['job_class'])
end
- it 'does not get "Rufus::Scheduler::ZoTime/EtOrbi::EoTime into an exact number"' do
+ it 'does not get any errors' do
expect { described_class.all.first.should_enque?(Time.now) }.not_to raise_error
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ca977effcb6..72684caad32 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -22,7 +22,7 @@ if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled)
require 'rspec_profiling/rspec'
end
-if ENV['CI'] && !ENV['NO_KNAPSACK']
+if ENV['CI'] && ENV['KNAPSACK_GENERATE_REPORT'] && !ENV['NO_KNAPSACK']
require 'knapsack'
Knapsack::Adapters::RSpecAdapter.bind
end
@@ -132,6 +132,11 @@ RSpec.configure do |config|
Gitlab::ReleaseBlogPost.instance.instance_variable_set(:@url, 'https://about.gitlab.com')
end
+ config.before(:example, :quarantine) do
+ # Skip tests in quarantine unless we explicitly focus on them.
+ skip('In quarantine') unless config.inclusion_filter[:quarantine]
+ end
+
config.before(:example, :request_store) do
RequestStore.begin!
end
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index dbdca99b5aa..0acc9e2a836 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -1,8 +1,16 @@
shared_examples 'issuable notes filter' do
+ let(:params) do
+ if issuable_parent.is_a?(Project)
+ { namespace_id: issuable_parent.namespace, project_id: issuable_parent, id: issuable.iid }
+ else
+ { group_id: issuable_parent, id: issuable.to_param }
+ end
+ end
+
it 'sets discussion filter' do
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
expect(user.reload.notes_filter_for(issuable)).to eq(notes_filter)
expect(UserPreference.count).to eq(1)
@@ -13,7 +21,7 @@ shared_examples 'issuable notes filter' do
expect_any_instance_of(issuable.class).to receive(:expire_note_etag_cache)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
end
it 'does not expires notes e-tag cache for issuable if filter did not change' do
@@ -22,14 +30,14 @@ shared_examples 'issuable notes filter' do
expect_any_instance_of(issuable.class).not_to receive(:expire_note_etag_cache)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
end
it 'does not set notes filter when database is in read only mode' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
expect(user.reload.notes_filter_for(issuable)).to eq(0)
end
@@ -37,7 +45,7 @@ shared_examples 'issuable notes filter' do
it 'returns only user comments' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
@@ -47,7 +55,7 @@ shared_examples 'issuable notes filter' do
it 'returns only activity notes' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
@@ -60,7 +68,7 @@ shared_examples 'issuable notes filter' do
expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
index 1f688c0f9d3..dcf7c1a90c2 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
@@ -32,11 +32,13 @@ shared_examples 'backfill migration for project repositories' do |storage|
it 'inserts rows in a single query' do
projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ group2 = namespaces.create!(name: 'gro', path: 'gro')
control_count = ActiveRecord::QueryRecorder.new { described_class.new.perform(1, projects.last.id) }
projects.create!(name: 'bar', path: 'bar', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
- projects.create!(name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ projects.create!(name: 'top', path: 'top', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ projects.create!(name: 'zoo', path: 'zoo', namespace_id: group2.id, storage_version: storage_version, repository_storage: shard.name)
expect { described_class.new.perform(1, projects.last.id) }.not_to exceed_query_limit(control_count)
end
diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb
new file mode 100644
index 00000000000..e3db10ed645
--- /dev/null
+++ b/spec/workers/remote_mirror_notification_worker_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe RemoteMirrorNotificationWorker, :mailer do
+ set(:project) { create(:project, :repository, :remote_mirror) }
+ set(:mirror) { project.remote_mirrors.first }
+
+ describe '#execute' do
+ it 'calls NotificationService#remote_mirror_update_failed when the mirror exists' do
+ mirror.update_column(:last_error, "There was a problem fetching")
+
+ expect(NotificationService).to receive_message_chain(:new, :remote_mirror_update_failed)
+
+ subject.perform(mirror.id)
+
+ expect(mirror.reload.error_notification_sent?).to be_truthy
+ end
+
+ it 'does nothing when the mirror has no errors' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(mirror.id)
+ end
+
+ it 'does nothing when the mirror does not exist' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(RemoteMirror.maximum(:id).to_i.succ)
+ end
+
+ it 'does nothing when a notification has already been sent' do
+ mirror.update_columns(last_error: "There was a problem fetching",
+ error_notification_sent: true)
+
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(mirror.id)
+ end
+ end
+end
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index d73b0b53713..b582a3650b6 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -22,6 +22,13 @@ describe RepositoryUpdateRemoteMirrorWorker do
expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.update_status }.to('finished')
end
+ it 'resets the notification flag upon success' do
+ expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
+ remote_mirror.update_column(:error_notification_sent, true)
+
+ expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.error_notification_sent }.to(false)
+ end
+
it 'sets status as failed when update remote mirror service executes with errors' do
error_message = 'fail!'
diff --git a/yarn.lock b/yarn.lock
index cde83c8e8a7..fadfeb3dc49 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7874,10 +7874,10 @@ prettier@1.13.7:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
-prettier@1.15.2:
- version "1.15.2"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.2.tgz#d31abe22afa4351efa14c7f8b94b58bb7452205e"
- integrity sha512-YgPLFFA0CdKL4Eg2IHtUSjzj/BWgszDHiNQAe0VAIBse34148whfdzLagRL+QiKS+YfK5ftB6X4v/MBw8yCoug==
+prettier@1.15.3:
+ version "1.15.3"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
+ integrity sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==
pretty-format@^23.6.0:
version "23.6.0"