summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcia Ramos <virtua.creative@gmail.com>2018-08-16 10:17:22 +0100
committerMarcia Ramos <virtua.creative@gmail.com>2018-08-16 10:17:22 +0100
commitbfc0c602072bd5b9bf73e117d0808043adc9465e (patch)
treeea55f6e86fdfdcccca9c593d5441c9d2c3efd441
parent1e23577417b3e4eb06b9aba1ec174dbe78b6b9be (diff)
parent0a666b2cb1020df4b8f7b71041b6bd109b49656a (diff)
downloadgitlab-ce-docs-processes.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into docs-processesdocs-processes
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.gitlab/issue_templates/Acceptance_Testing.md100
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md8
-rw-r--r--.rubocop.yml6
-rw-r--r--.rubocop_todo.yml6
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock23
-rw-r--r--Gemfile.rails5.lock5
-rw-r--r--PROCESS.md84
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js8
-rw-r--r--app/assets/javascripts/boards/models/list.js3
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/diffs/components/app.vue12
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue16
-rw-r--r--app/assets/javascripts/diffs/constants.js3
-rw-r--r--app/assets/javascripts/diffs/store/actions.js21
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js43
-rw-r--r--app/assets/javascripts/emoji/support/unicode_support_map.js2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue2
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/button.vue11
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue31
-rw-r--r--app/assets/javascripts/ide/ide_router.js100
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js3
-rw-r--r--app/assets/javascripts/ide/stores/actions/merge_request.js68
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js32
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/actions.js3
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js7
-rw-r--r--app/assets/javascripts/jobs/components/artifacts_block.vue98
-rw-r--r--app/assets/javascripts/jobs/components/erased_block.vue48
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue33
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_details_block.vue38
-rw-r--r--app/assets/javascripts/jobs/components/stuck_block.vue63
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js2
-rw-r--r--app/assets/javascripts/labels_select.js9
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js94
-rw-r--r--app/assets/javascripts/project_select.js8
-rw-r--r--app/assets/javascripts/reports/components/grouped_test_reports_app.vue8
-rw-r--r--app/assets/javascripts/reports/components/issue_body.js (renamed from app/assets/javascripts/vue_shared/components/reports/issue_body.js)2
-rw-r--r--app/assets/javascripts/reports/components/issue_status_icon.vue (renamed from app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue)3
-rw-r--r--app/assets/javascripts/reports/components/issues_list.vue (renamed from app/assets/javascripts/vue_shared/components/reports/issues_list.vue)4
-rw-r--r--app/assets/javascripts/reports/components/modal_open_name.vue (renamed from app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue)0
-rw-r--r--app/assets/javascripts/reports/components/report_issues.vue (renamed from app/assets/javascripts/vue_shared/components/reports/report_issues.vue)4
-rw-r--r--app/assets/javascripts/reports/components/report_link.vue (renamed from app/assets/javascripts/vue_shared/components/reports/report_link.vue)0
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue (renamed from app/assets/javascripts/vue_shared/components/reports/report_section.vue)2
-rw-r--r--app/assets/javascripts/reports/components/summary_row.vue (renamed from app/assets/javascripts/vue_shared/components/reports/summary_row.vue)2
-rw-r--r--app/assets/javascripts/reports/constants.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/reports/constants.js3
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss3
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss1
-rw-r--r--app/assets/stylesheets/framework/buttons.scss6
-rw-r--r--app/assets/stylesheets/framework/common.scss6
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/emojis.scss2
-rw-r--r--app/assets/stylesheets/framework/files.scss6
-rw-r--r--app/assets/stylesheets/framework/filters.scss4
-rw-r--r--app/assets/stylesheets/framework/flash.scss4
-rw-r--r--app/assets/stylesheets/framework/forms.scss4
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/modal.scss1
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss10
-rw-r--r--app/assets/stylesheets/framework/variables.scss37
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss16
-rw-r--r--app/assets/stylesheets/framework/zen.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss2
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/commits.scss4
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss4
-rw-r--r--app/assets/stylesheets/pages/editor.scss25
-rw-r--r--app/assets/stylesheets/pages/environments.scss4
-rw-r--r--app/assets/stylesheets/pages/graph.scss4
-rw-r--r--app/assets/stylesheets/pages/groups.scss3
-rw-r--r--app/assets/stylesheets/pages/issuable.scss10
-rw-r--r--app/assets/stylesheets/pages/issues.scss4
-rw-r--r--app/assets/stylesheets/pages/labels.scss9
-rw-r--r--app/assets/stylesheets/pages/milestone.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss10
-rw-r--r--app/assets/stylesheets/pages/notes.scss28
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss23
-rw-r--r--app/assets/stylesheets/pages/search.scss6
-rw-r--r--app/assets/stylesheets/pages/settings.scss14
-rw-r--r--app/assets/stylesheets/performance_bar.scss2
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/notification_settings_controller.rb8
-rw-r--r--app/controllers/projects/autocomplete_sources_controller.rb11
-rw-r--r--app/finders/license_template_finder.rb36
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/avatars_helper.rb52
-rw-r--r--app/helpers/blob_helper.rb12
-rw-r--r--app/helpers/button_helper.rb6
-rw-r--r--app/helpers/groups_helper.rb5
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/mirror_helper.rb5
-rw-r--r--app/helpers/notifications_helper.rb12
-rw-r--r--app/helpers/projects_helper.rb5
-rw-r--r--app/mailers/abuse_report_mailer.rb2
-rw-r--r--app/mailers/base_mailer.rb2
-rw-r--r--app/mailers/devise_mailer.rb9
-rw-r--r--app/mailers/email_rejection_mailer.rb2
-rw-r--r--app/mailers/emails/issues.rb2
-rw-r--r--app/mailers/emails/members.rb2
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/mailers/emails/pages_domains.rb2
-rw-r--r--app/mailers/emails/pipelines.rb8
-rw-r--r--app/mailers/emails/profile.rb2
-rw-r--r--app/mailers/emails/projects.rb2
-rw-r--r--app/mailers/notify.rb16
-rw-r--r--app/mailers/previews/devise_mailer_preview.rb2
-rw-r--r--app/mailers/previews/email_rejection_mailer_preview.rb2
-rw-r--r--app/mailers/previews/notify_preview.rb2
-rw-r--r--app/mailers/previews/repository_check_mailer_preview.rb2
-rw-r--r--app/mailers/repository_check_mailer.rb2
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/ci/job_artifact.rb2
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/license_template.rb53
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/models/notification_setting.rb9
-rw-r--r--app/models/programming_language.rb2
-rw-r--r--app/models/project.rb22
-rw-r--r--app/models/project_services/asana_service.rb2
-rw-r--r--app/models/project_services/assembla_service.rb2
-rw-r--r--app/models/project_services/bamboo_service.rb2
-rw-r--r--app/models/project_services/bugzilla_service.rb2
-rw-r--r--app/models/project_services/buildkite_service.rb2
-rw-r--r--app/models/project_services/builds_email_service.rb2
-rw-r--r--app/models/project_services/campfire_service.rb6
-rw-r--r--app/models/project_services/chat_message/base_message.rb2
-rw-r--r--app/models/project_services/chat_message/issue_message.rb2
-rw-r--r--app/models/project_services/chat_message/merge_message.rb2
-rw-r--r--app/models/project_services/chat_message/note_message.rb2
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb2
-rw-r--r--app/models/project_services/chat_message/push_message.rb2
-rw-r--r--app/models/project_services/chat_message/wiki_page_message.rb2
-rw-r--r--app/models/project_services/chat_notification_service.rb2
-rw-r--r--app/models/project_services/ci_service.rb2
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/deployment_service.rb2
-rw-r--r--app/models/project_services/drone_ci_service.rb2
-rw-r--r--app/models/project_services/emails_on_push_service.rb2
-rw-r--r--app/models/project_services/external_wiki_service.rb2
-rw-r--r--app/models/project_services/flowdock_service.rb2
-rw-r--r--app/models/project_services/gemnasium_service.rb2
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/hangouts_chat_service.rb2
-rw-r--r--app/models/project_services/hipchat_service.rb22
-rw-r--r--app/models/project_services/irker_service.rb2
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/kubernetes_service.rb2
-rw-r--r--app/models/project_services/mattermost_service.rb2
-rw-r--r--app/models/project_services/mattermost_slash_commands_service.rb2
-rw-r--r--app/models/project_services/microsoft_teams_service.rb2
-rw-r--r--app/models/project_services/mock_ci_service.rb2
-rw-r--r--app/models/project_services/mock_deployment_service.rb2
-rw-r--r--app/models/project_services/mock_monitoring_service.rb2
-rw-r--r--app/models/project_services/monitoring_service.rb2
-rw-r--r--app/models/project_services/packagist_service.rb2
-rw-r--r--app/models/project_services/pipelines_email_service.rb2
-rw-r--r--app/models/project_services/pivotaltracker_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb2
-rw-r--r--app/models/project_services/pushover_service.rb4
-rw-r--r--app/models/project_services/redmine_service.rb2
-rw-r--r--app/models/project_services/slack_service.rb2
-rw-r--r--app/models/project_services/slack_slash_commands_service.rb2
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/models/project_services/teamcity_service.rb2
-rw-r--r--app/models/protected_branch/merge_access_level.rb2
-rw-r--r--app/models/protected_branch/push_access_level.rb2
-rw-r--r--app/models/protected_tag/create_access_level.rb2
-rw-r--r--app/models/repository_language.rb2
-rw-r--r--app/models/site_statistic.rb2
-rw-r--r--app/models/storage/hashed_project.rb2
-rw-r--r--app/models/storage/legacy_project.rb2
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/user.rb19
-rw-r--r--app/policies/commit_policy.rb5
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/presenters/ci/build_runner_presenter.rb2
-rw-r--r--app/serializers/environment_entity.rb11
-rw-r--r--app/serializers/project_mirror_serializer.rb5
-rw-r--r--app/serializers/test_case_entity.rb2
-rw-r--r--app/serializers/test_reports_comparer_entity.rb2
-rw-r--r--app/serializers/test_reports_comparer_serializer.rb2
-rw-r--r--app/serializers/test_suite_comparer_entity.rb2
-rw-r--r--app/services/ci/enqueue_build_service.rb8
-rw-r--r--app/services/ci/process_pipeline_service.rb6
-rw-r--r--app/services/commits/tag_service.rb26
-rw-r--r--app/services/notes/quick_actions_service.rb3
-rw-r--r--app/services/notification_recipient_service.rb12
-rw-r--r--app/services/preview_markdown_service.rb12
-rw-r--r--app/services/projects/autocomplete_service.rb10
-rw-r--r--app/services/projects/detect_repository_languages_service.rb2
-rw-r--r--app/services/quick_actions/interpret_service.rb47
-rw-r--r--app/services/quick_actions/target_service.rb30
-rw-r--r--app/services/system_note_service.rb15
-rw-r--r--app/services/tags/create_service.rb2
-rw-r--r--app/services/todos/destroy/base_service.rb2
-rw-r--r--app/services/todos/destroy/confidential_issue_service.rb2
-rw-r--r--app/services/todos/destroy/entity_leave_service.rb2
-rw-r--r--app/services/todos/destroy/group_private_service.rb2
-rw-r--r--app/services/todos/destroy/private_features_service.rb2
-rw-r--r--app/services/todos/destroy/project_private_service.rb2
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml6
-rw-r--r--app/views/admin/application_settings/show.html.haml2
-rw-r--r--app/views/admin/spam_logs/index.html.haml2
-rw-r--r--app/views/admin/users/show.html.haml12
-rw-r--r--app/views/projects/blob/_editor.html.haml8
-rw-r--r--app/views/projects/forks/_fork_button.html.haml4
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml7
-rw-r--r--app/views/projects/mirrors/_instructions.html.haml15
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml63
-rw-r--r--app/views/projects/mirrors/_mirror_repos_form.html.haml18
-rw-r--r--app/views/projects/mirrors/_push.html.haml50
-rw-r--r--app/views/projects/mirrors/_show.html.haml4
-rw-r--r--app/views/projects/pages/_use.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml4
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/shared/_remote_mirror_update_button.html.haml19
-rw-r--r--app/views/shared/milestones/_issuables.html.haml4
-rw-r--r--app/views/shared/notifications/_custom_notifications.html.haml2
-rw-r--r--app/views/shared/plugins/_index.html.haml4
-rw-r--r--app/views/shared/runners/_form.html.haml30
-rw-r--r--app/workers/detect_repository_languages_worker.rb2
-rw-r--r--app/workers/todos_destroyer/confidential_issue_worker.rb2
-rw-r--r--app/workers/todos_destroyer/entity_leave_worker.rb2
-rw-r--r--app/workers/todos_destroyer/group_private_worker.rb2
-rw-r--r--app/workers/todos_destroyer/private_features_worker.rb2
-rw-r--r--app/workers/todos_destroyer/project_private_worker.rb2
-rwxr-xr-xbin/secpick4
-rw-r--r--changelogs/unreleased/25990-web-terminal-improvements.yml5
-rw-r--r--changelogs/unreleased/2747-protected-environments-backend-ce.yml5
-rw-r--r--changelogs/unreleased/28930-add-project-reference-filter.yml5
-rw-r--r--changelogs/unreleased/41996-copy-to-clipboard-tooltip-appears-under-modal.yml5
-rw-r--r--changelogs/unreleased/45663-tag-quick-action-on-commit-comments.yml5
-rw-r--r--changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml5
-rw-r--r--changelogs/unreleased/48320-cancel-a-created-job.yml5
-rw-r--r--changelogs/unreleased/48942-rename-backlog-list-to-open-issue-boards.yml5
-rw-r--r--changelogs/unreleased/48967-disable-statement-timeout.yml5
-rw-r--r--changelogs/unreleased/49905-fix-checkboxes-runners.yml5
-rw-r--r--changelogs/unreleased/49953-add-user_show_add_ssh_key_message-setting.yml5
-rw-r--r--changelogs/unreleased/50047-spam-logs-pagination.yml5
-rw-r--r--changelogs/unreleased/50063-add-missing-i18n-strings-to-issue-boards.yml5
-rw-r--r--changelogs/unreleased/50101-aritfacts-block.yml5
-rw-r--r--changelogs/unreleased/50101-erased-block.yml5
-rw-r--r--changelogs/unreleased/50101-job-log-component.yml5
-rw-r--r--changelogs/unreleased/50101-stuck-component.yml5
-rw-r--r--changelogs/unreleased/50126-blocked-user-card.yml5
-rw-r--r--changelogs/unreleased/50180-fa-icon-google-audit.yml5
-rw-r--r--changelogs/unreleased/50243-auto-devops-behind-a-proxy.yml6
-rw-r--r--changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml5
-rw-r--r--changelogs/unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml5
-rw-r--r--changelogs/unreleased/add_google_noto_color_emoji_font.yml5
-rw-r--r--changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml5
-rw-r--r--changelogs/unreleased/bvl-add-czech.yml5
-rw-r--r--changelogs/unreleased/bvl-merge-base-api.yml5
-rw-r--r--changelogs/unreleased/ce-5666-optimize_querying_manageable_groups.yml5
-rw-r--r--changelogs/unreleased/expose-all-artifacts-sizes-in-jobs-api.yml5
-rw-r--r--changelogs/unreleased/feat-add-default-avatar-to-group.yml5
-rw-r--r--changelogs/unreleased/fix-labels-list-item-height-with-no-description.yml5
-rw-r--r--changelogs/unreleased/fix-pipeline-fixture-seeder.yml5
-rw-r--r--changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml5
-rw-r--r--changelogs/unreleased/frozen-string-enable-app-mailers.yml5
-rw-r--r--changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml5
-rw-r--r--changelogs/unreleased/frozen-string-enable-app-vestigial.yml5
-rw-r--r--changelogs/unreleased/gitaly-install-path.yml5
-rw-r--r--changelogs/unreleased/ide-delete-new-files-state.yml5
-rw-r--r--changelogs/unreleased/ide-header-buttons-tooltip.yml5
-rw-r--r--changelogs/unreleased/ide-open-empty-merge-request.yml5
-rw-r--r--changelogs/unreleased/mk-bump-rainbow-gem.yml5
-rw-r--r--changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml6
-rw-r--r--changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml5
-rw-r--r--changelogs/unreleased/sh-bump-rugged-0-27-4.yml5
-rw-r--r--changelogs/unreleased/tz-mr-incremental-rendering.yml4
-rw-r--r--changelogs/unreleased/visual-improvements-language-bar.yml5
-rw-r--r--db/fixtures/development/14_pipelines.rb63
-rw-r--r--db/fixtures/development/23_spam_logs.rb32
-rw-r--r--db/migrate/20160317092222_add_moved_to_to_issue.rb2
-rw-r--r--db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb8
-rw-r--r--db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb5
-rw-r--r--db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb26
-rw-r--r--db/migrate/20180113220114_rework_redirect_routes_indexes.rb66
-rw-r--r--db/migrate/20180403035759_create_project_ci_cd_settings.rb16
-rw-r--r--db/migrate/20180420010616_cleanup_build_stage_migration.rb76
-rw-r--r--db/migrate/20180504195842_project_name_lower_index.rb18
-rw-r--r--db/migrate/20180702124358_remove_orphaned_routes.rb20
-rw-r--r--db/migrate/20180808162000_add_user_show_add_ssh_key_message_to_application_settings.rb19
-rw-r--r--db/optional_migrations/composite_primary_keys.rb14
-rw-r--r--db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb6
-rw-r--r--db/post_migrate/20170503004427_update_retried_for_ci_build.rb6
-rw-r--r--db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb18
-rw-r--r--db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb16
-rw-r--r--db/post_migrate/20170526185842_migrate_pipeline_stages.rb24
-rw-r--r--db/post_migrate/20170526190000_migrate_build_stage_reference_again.rb14
-rw-r--r--db/post_migrate/20170711145558_migrate_stages_statuses.rb8
-rw-r--r--db/post_migrate/20171207150343_remove_soft_removed_objects.rb12
-rw-r--r--db/post_migrate/20180119121225_remove_redundant_pipeline_stages.rb40
-rw-r--r--db/post_migrate/20180305100050_remove_permanent_from_redirect_routes.rb16
-rw-r--r--db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb8
-rw-r--r--db/post_migrate/20180405101928_reschedule_builds_stages_migration.rb14
-rw-r--r--db/post_migrate/20180420080616_schedule_stages_index_migration.rb14
-rw-r--r--db/post_migrate/20180604123514_cleanup_stages_position_migration.rb36
-rw-r--r--db/schema.rb3
-rw-r--r--doc/README.md1
-rw-r--r--doc/administration/operations/ssh_certificates.md17
-rw-r--r--doc/api/boards.md2
-rw-r--r--doc/api/events.md2
-rw-r--r--doc/api/group_boards.md2
-rw-r--r--doc/api/jobs.md28
-rw-r--r--doc/api/notification_settings.md2
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/api/repositories.md36
-rw-r--r--doc/api/settings.md8
-rw-r--r--doc/api/system_hooks.md5
-rw-r--r--doc/ci/examples/README.md4
-rw-r--r--doc/ci/img/junit_test_report.pngbin0 -> 9572 bytes
-rw-r--r--doc/ci/junit_test_reports.md102
-rw-r--r--doc/ci/yaml/README.md46
-rw-r--r--doc/development/contributing/design.md19
-rw-r--r--doc/development/diffs.md17
-rw-r--r--doc/development/feature_flags.md35
-rw-r--r--doc/development/migration_style_guide.md28
-rw-r--r--doc/development/new_fe_guide/development/testing.md24
-rw-r--r--doc/install/installation.md6
-rw-r--r--doc/user/markdown.md5
-rw-r--r--doc/user/project/img/issue_board.pngbin100684 -> 327718 bytes
-rw-r--r--doc/user/project/integrations/webhooks.md1
-rw-r--r--doc/user/project/issue_board.md8
-rw-r--r--doc/user/project/merge_requests/index.md3
-rw-r--r--doc/user/project/quick_actions.md7
-rw-r--r--doc/user/project/web_ide/img/admin_clientside_evaluation.pngbin0 -> 9342 bytes
-rw-r--r--doc/user/project/web_ide/img/clientside_evaluation.pngbin0 -> 60256 bytes
-rw-r--r--doc/user/project/web_ide/index.md34
-rw-r--r--lib/api/entities.rb10
-rw-r--r--lib/api/jobs.rb4
-rw-r--r--lib/api/notification_settings.rb8
-rw-r--r--lib/api/repositories.rb33
-rw-r--r--lib/api/templates.rb44
-rw-r--r--lib/banzai/filter/project_reference_filter.rb117
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/reference_parser/project_parser.rb30
-rw-r--r--lib/gitlab/database/migration_helpers.rb103
-rw-r--r--lib/gitlab/git.rb8
-rw-r--r--lib/gitlab/git/merge_base.rb44
-rw-r--r--lib/gitlab/git/repository.rb13
-rw-r--r--lib/gitlab/git/repository_mirroring.rb29
-rw-r--r--lib/gitlab/i18n.rb3
-rw-r--r--lib/gitlab/import_export/relation_factory.rb6
-rw-r--r--lib/gitlab/setup_helper.rb13
-rw-r--r--lib/gitlab/template/finders/base_template_finder.rb2
-rw-r--r--lib/gitlab/template/finders/repo_template_finder.rb10
-rw-r--r--lib/tasks/gitlab/gitaly.rake24
-rw-r--r--locale/gitlab.pot132
-rw-r--r--locale/unfound_translations.rb16
-rw-r--r--qa/qa/specs/features/project/auto_devops_spec.rb2
-rw-r--r--rubocop/cop/migration/add_reference.rb49
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/application_controller_spec.rb4
-rw-r--r--spec/controllers/notification_settings_controller_spec.rb9
-rw-r--r--spec/factories/uploads.rb7
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/commits/user_uses_slash_commands_spec.rb48
-rw-r--r--spec/features/issues/award_emoji_spec.rb146
-rw-r--r--spec/features/issues/award_spec.rb51
-rw-r--r--spec/features/issues/user_interacts_with_awards_spec.rb347
-rw-r--r--spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb2
-rw-r--r--spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb172
-rw-r--r--spec/features/projects/blobs/shortcuts_blob_spec.rb4
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb3
-rw-r--r--spec/features/projects/files/user_deletes_files_spec.rb6
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb22
-rw-r--r--spec/features/projects/files/user_replaces_files_spec.rb8
-rw-r--r--spec/features/projects/remote_mirror_spec.rb10
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb5
-rw-r--r--spec/finders/license_template_finder_spec.rb49
-rw-r--r--spec/helpers/avatars_helper_spec.rb65
-rw-r--r--spec/helpers/button_helper_spec.rb12
-rw-r--r--spec/helpers/groups_helper_spec.rb13
-rw-r--r--spec/helpers/icons_helper_spec.rb20
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js10
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js1
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js18
-rw-r--r--spec/javascripts/ide/components/new_dropdown/button_spec.js16
-rw-r--r--spec/javascripts/ide/components/preview/clientside_spec.js6
-rw-r--r--spec/javascripts/ide/stores/actions/merge_request_spec.js101
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js52
-rw-r--r--spec/javascripts/ide/stores/modules/branches/actions_spec.js16
-rw-r--r--spec/javascripts/ide/stores/mutations_spec.js27
-rw-r--r--spec/javascripts/jobs/artifacts_block_spec.js120
-rw-r--r--spec/javascripts/jobs/erased_block_spec.js56
-rw-r--r--spec/javascripts/jobs/job_log_spec.js45
-rw-r--r--spec/javascripts/jobs/sidebar_details_block_spec.js67
-rw-r--r--spec/javascripts/jobs/stuck_block_spec.js81
-rw-r--r--spec/javascripts/reports/components/modal_open_name_spec.js (renamed from spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js)2
-rw-r--r--spec/javascripts/reports/components/report_link_spec.js (renamed from spec/javascripts/vue_shared/components/reports/report_link_spec.js)4
-rw-r--r--spec/javascripts/reports/components/report_section_spec.js (renamed from spec/javascripts/vue_shared/components/reports/report_section_spec.js)2
-rw-r--r--spec/javascripts/reports/components/summary_row_spec.js (renamed from spec/javascripts/vue_shared/components/reports/summary_row_spec.js)2
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb85
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb85
-rw-r--r--spec/lib/banzai/reference_parser/project_parser_spec.rb50
-rw-r--r--spec/lib/gitlab/cleanup/project_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb86
-rw-r--r--spec/lib/gitlab/git/merge_base_spec.rb95
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb37
-rw-r--r--spec/models/ci/build_spec.rb6
-rw-r--r--spec/models/internal_id_spec.rb4
-rw-r--r--spec/models/license_template_spec.rb59
-rw-r--r--spec/models/notification_setting_spec.rb36
-rw-r--r--spec/models/project_spec.rb9
-rw-r--r--spec/models/repository_spec.rb42
-rw-r--r--spec/requests/api/jobs_spec.rb74
-rw-r--r--spec/requests/api/repositories_spec.rb73
-rw-r--r--spec/requests/api/templates_spec.rb3
-rw-r--r--spec/rubocop/cop/migration/add_reference_spec.rb54
-rw-r--r--spec/serializers/project_mirror_serializer_spec.rb7
-rw-r--r--spec/services/ci/enqueue_build_service_spec.rb16
-rw-r--r--spec/services/commits/tag_service_spec.rb102
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb46
-rw-r--r--spec/services/preview_markdown_service_spec.rb25
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb107
-rw-r--r--spec/services/quick_actions/target_service_spec.rb83
-rw-r--r--spec/services/system_note_service_spec.rb19
-rw-r--r--spec/support/banzai/reference_filter_shared_examples.rb73
-rw-r--r--spec/support/helpers/features/notes_helpers.rb7
-rw-r--r--spec/support/helpers/test_env.rb12
-rw-r--r--spec/support/import_export/configuration_helper.rb2
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb67
-rw-r--r--spec/views/shared/notes/_form.html.haml_spec.rb10
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml31
-rw-r--r--vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml13
440 files changed, 5539 insertions, 1760 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fcf47421b01..fd02d72b4c2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -453,6 +453,7 @@ danger-review:
- master
variables:
- $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/
+ - $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
script:
- git version
- danger --fail-on-errors=true
diff --git a/.gitlab/issue_templates/Acceptance_Testing.md b/.gitlab/issue_templates/Acceptance_Testing.md
new file mode 100644
index 00000000000..f1fbb96ce61
--- /dev/null
+++ b/.gitlab/issue_templates/Acceptance_Testing.md
@@ -0,0 +1,100 @@
+## Details
+- **Feature Toggle Name**: `FEATURE_NAME`
+- **Required GitLab Version**: `vX.X`
+
+--------------------------------------------------------------------------------
+
+## 1. Preparation
+
+- [ ] **Controllers and workers**:
+ 1. Please link to dashboards of the workers, and the controllers and actions that can be impacted
+ 2. ...
+ 3. ...
+
+## 2. Development Trial
+
+#### Check Dev Server Versions
+- [ ] GitLab: https://dev.gitlab.org/help
+
+#### Enable on `dev.gitlab.org`:
+- [ ] `/chatops feature set FEATURE_NAME true --dev` in [`#dev-gitlab`](https://gitlab.slack.com/messages/C6WQ87MU3)
+
+Then leave running while monitoring and performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved)
+
+## 2. Staging Trial
+
+#### Check Staging Server Versions
+- [ ] GitLab: https://staging.gitlab.com/help
+
+#### Enable on `staging.gitlab.com`
+- [ ] `/chatops run feature set FEATURE_NAME true --staging` in [`#development`](https://gitlab.slack.com/messages/C02PF508L/)
+
+Then leave running while monitoring for at least **15 minutes** while performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+
+## 4. Production Server Version Check
+
+- [ ] GitLab: https://gitlab.com/help
+
+## 5. Initial Impact Check
+
+- [ ] Enable for a subset of users, when using percentage gates: 1%.
+
+Then leave running while monitoring for at least **15 minutes** while performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+
+## 6. Low Impact Check
+
+- [ ] Enable for a bigger subset of users, when using percentage gates: 10%.
+
+Then leave running while monitoring for at least **30 minutes** while performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+
+## 7. Mid Impact Trial
+
+- [ ] Enable for a big subset of users, when using percentage gates: 50%.
+
+Then leave running while monitoring for at least **12 hours** while performing some testing through web, api or SSH.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+
+## 8. Full Impact Trial
+
+- [ ] Enable for all users: `/chatops run feature set FEATURE_NAME true
+
+Then leave running while monitoring for at least **1 week**.
+
+#### Monitor
+
+- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
+- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
+- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved)
+
+#### Success?
+
+- [ ] Remove the feature gate from the code, and close this issue with that MR.
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index c1f702e9385..64b54b171f7 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -12,7 +12,7 @@ Set the title to: `[Security] Description of the original issue`
- [ ] Link to the original issue adding it to the [links section](#links)
- [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org`
- [ ] Create an MR targetting `org` `master`, prefixing your branch with `security-`
-- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]`
+- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]`
- [ ] Add a link to the MR to the [links section](#links)
- [ ] Add a link to an EE MR if required
- [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**.
@@ -22,13 +22,13 @@ Set the title to: `[Security] Description of the original issue`
- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases
- [ ] At this point, it might be easy to squash the commits from the MR into one
- - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [seckpick documentation]
+ - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
- [ ] Create each MR targetting the security branch `security-X-Y`
- [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR
- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager.
-[seckpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script
+[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script
#### Documentation and final details
@@ -68,4 +68,4 @@ Set the title to: `[Security] Description of the original issue`
[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
[RM list]: https://about.gitlab.com/release-managers/
-/label ~security
+/label ~security
diff --git a/.rubocop.yml b/.rubocop.yml
index c8b1ce327e2..9858bbe0ddd 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -31,6 +31,10 @@ Style/MutableConstant:
- 'ee/db/post_migrate/**/*'
- 'ee/db/geo/migrate/**/*'
+# TODO: Move this to gitlab-styles
+Style/SafeNavigation:
+ Enabled: false
+
Naming/FileName:
ExpectMatchingDefinition: true
Exclude:
@@ -44,6 +48,8 @@ Naming/FileName:
- 'qa/bin/*'
- 'config/**/*'
- 'lib/generators/**/*'
+ - 'locale/unfound_translations.rb'
+ - 'ee/locale/unfound_translations.rb'
- 'ee/lib/generators/**/*'
IgnoreExecutableScripts: true
AllowedAcronyms:
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 8a1ca6747a8..54e3b8217d8 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -706,12 +706,6 @@ Style/RescueModifier:
Style/RescueStandardError:
Enabled: false
-# Offense count: 92
-# Cop supports --auto-correct.
-# Configuration parameters: ConvertCodeThatCanStartToReturnNil.
-Style/SafeNavigation:
- Enabled: false
-
# Offense count: 8
# Cop supports --auto-correct.
Style/SelfAssignment:
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 8104cabd36f..0e79152459e 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.1.0
+8.1.1
diff --git a/Gemfile b/Gemfile
index d9066081f74..5666e6cebc5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -180,7 +180,7 @@ gem 'rufus-scheduler', '~> 3.4'
gem 'httparty', '~> 0.13.3'
# Colored output to console
-gem 'rainbow', '~> 2.2'
+gem 'rainbow', '~> 3.0'
# Progress bar
gem 'ruby-progressbar'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1537cacaadd..b33dd75c278 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -123,7 +123,7 @@ GEM
numerizer (~> 0.1.1)
chunky_png (1.3.5)
citrus (3.0.2)
- coderay (1.1.1)
+ coderay (1.1.2)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
commonmarker (0.17.8)
@@ -494,7 +494,7 @@ GEM
memoist (0.16.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
- method_source (0.8.2)
+ method_source (0.9.0)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
@@ -636,12 +636,11 @@ GEM
unparser
procto (0.0.3)
prometheus-client-mmap (0.9.4)
- pry (0.10.4)
+ pry (0.11.3)
coderay (~> 1.1.0)
- method_source (~> 0.8.1)
- slop (~> 3.4)
- pry-byebug (3.4.2)
- byebug (~> 9.0)
+ method_source (~> 0.9.0)
+ pry-byebug (3.4.3)
+ byebug (>= 9.0, < 9.1)
pry (~> 0.10)
pry-rails (0.3.5)
pry (>= 0.9.10)
@@ -692,8 +691,7 @@ GEM
activesupport (= 4.2.10)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rainbow (2.2.2)
- rake
+ rainbow (3.0.0)
raindrops (0.18.0)
rake (12.3.1)
rb-fsevent (0.10.2)
@@ -811,7 +809,7 @@ GEM
rubyzip (1.2.1)
rufus-scheduler (3.4.0)
et-orbi (~> 1.0)
- rugged (0.27.2)
+ rugged (0.27.4)
safe_yaml (1.0.4)
sanitize (4.6.6)
crass (~> 1.0.2)
@@ -872,7 +870,6 @@ GEM
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slack-notifier (1.5.1)
- slop (3.6.0)
spring (2.0.1)
activesupport (>= 4.2)
spring-commands-rspec (1.0.4)
@@ -1136,7 +1133,7 @@ DEPENDENCIES
rails (= 4.2.10)
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9)
- rainbow (~> 2.2)
+ rainbow (~> 3.0)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbtrace (~> 0.4)
@@ -1207,4 +1204,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.16.2
+ 1.16.3
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 39305927c0f..af70e2c1939 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -701,8 +701,7 @@ GEM
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rainbow (2.2.2)
- rake
+ rainbow (3.0.0)
raindrops (0.18.0)
rake (12.3.1)
rb-fsevent (0.10.2)
@@ -1147,7 +1146,7 @@ DEPENDENCIES
rails-controller-testing
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 5.1)
- rainbow (~> 2.2)
+ rainbow (~> 3.0)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbtrace (~> 0.4)
diff --git a/PROCESS.md b/PROCESS.md
index eddc0dec3e5..583f36b820f 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -15,8 +15,9 @@
- [Between the 1st and the 7th](#between-the-1st-and-the-7th)
- [On the 7th](#on-the-7th)
- [After the 7th](#after-the-7th)
-- [Regressions](#regressions)
- - [How to manage a regression](#how-to-manage-a-regression)
+- [Bugs](#bugs)
+ - [Regressions](#regressions)
+ - [Managing bugs](#managing-bugs)
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
- [Retrospective](#retrospective)
- [Kickoff](#kickoff)
@@ -185,7 +186,7 @@ For more information read the process for
Once the stable branch is frozen, the only MRs that can be cherry-picked into
the stable branch are:
-* Fixes for [regressions](#regressions)
+* Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing bugs](#managing-bugs) section.
* Fixes for security issues
* Fixes or improvements to automated QA scenarios
* [Documentation updates](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late) for changes in the same release
@@ -218,48 +219,59 @@ you can ask for an exception to be made.
Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md) about how to open an exception request before opening one.
-## Regressions
+## Bugs
-A regression for a particular monthly release is a bug that exists in that
-release, but wasn't present in the release before. This includes bugs in
-features that were only added in that monthly release. Every regression **must**
-have the milestone of the release it was introduced in - if a regression doesn't
-have a milestone, it might be 'just' a bug!
+A ~bug is a defect, error, failure which causes the system to behave incorrectly or prevents it from fulfilling the product requirements.
-For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
-then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
-reintroduces the bug, then this bug is still a regression in 10.5.
+The level of impact of a ~bug can vary from blocking a whole functionality
+or a feature usability bug. A bug should always be linked to a severity level.
+Refer to our [severity levels](../CONTRIBUTING.md#severity-labels)
-Because GitLab.com runs release candidates of new releases, a regression can be
-reported in a release before its 'official' release date on the 22nd of the
-month. When we say 'the most recent monthly release', this can refer to either
-the version currently running on GitLab.com, or the most recent version
-available in the package repositories.
+Whether the bug is also a regression or not, the triage process should start as soon as possible.
+Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed.
-### How to manage a regression
+### Regressions
-Regressions are very important, and they should be considered high priority
-issues that should be solved as soon as possible, especially if they affect
-users. Despite that, ~regression label itself does not imply when the issue
-will be scheduled.
+A ~regression implies that a previously **verified working functionality** no longer works.
+Regressions are a subset of bugs. We use the ~regression label to imply that the defect caused the functionality to regress.
+The label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule.
-When a regression is found:
-1. Create an issue describing the problem in the most detailed way possible
-1. If possible, provide links to real examples and how to reproduce the problem
+The regression label does not apply to ~bugs for new features for which functionality was **never verified as working**.
+These, by definition, are not regressions.
+
+A regression should always have the `regression:xx.x` label on it to designate when it was introduced.
+
+Regressions should be considered high priority issues that should be solved as soon as possible, especially if they have severe impact on users.
+
+### Managing bugs
+
+**Prioritization:** We give higher priority to regressions on features that worked in the last recent monthly release and the current release candidates.
+The two scenarios below can [bypass the exception request in the release process](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md#after-the-7th), where the affected regression version matches the current monthly release version.
+* A regression which worked in the **Last monthly release**
+ * **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label.
+ * *Note:* When we say `the last recent monthly release`, this can refer to either the version currently running on GitLab.com, or the most recent version available in the package repositories.
+* A regression which worked in the **Current release candidates**
+ * **Example:** In 11.1-RC3 we shipped a new feature which has been verified as working. Then in 11.1-RC5 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label.
+ * *Note:* Because GitLab.com runs release candidates of new releases, a regression can be reported in a release before its 'official' release date on the 22nd of the month.
+
+When a bug is found:
+1. Create an issue describing the problem in the most detailed way possible.
+1. If possible, provide links to real examples and how to reproduce the problem.
1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels),
the [subject label](../CONTRIBUTING.md#subject-labels)
and any other label that may apply in the specific case
-1. Add the ~bug and ~regression labels
-1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels).
-1. If the regression is either an ~S1, ~S2 or ~S3 severity, label the regression with the current milestone as it should be fixed in the current milestone.
- 1. If the regression was introduced in an RC of the current release, label with ~Deliverable
- 1. If the regression was introduced in the previous release, label with ~"Next Patch Release"
-1. If the regression is an ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager.
-
-When a new issue is found, the fix should start as soon as possible. You can
-ping the Engineering Manager or the Product Manager for the relative area to
-make them aware of the issue earlier. They will analyze the priority and change
-it if needed.
+1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](../CONTRIBUTING.md#bug-severity-labels) and [Priority label](../CONTRIBUTING.md#bug-priority-labels).
+The counterpart Product Manager is included to weigh-in on prioritization as needed.
+1. If the ~bug is **NOT** a regression:
+ 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied.
+1. If the bug is a ~regression:
+ 1. Determine the release that the regression affects and add the corresponding `regression:xx.x` label.
+ 1. If the affected release version can't be determined, add the generic ~regression label for the time being.
+ 1. If the affected version `xx.x` in `regression:xx.x` is the **current release**, it's recommended to schedule the fix for the current milestone.
+ 1. This falls under regressions which worked in the last release and the current RCs. More detailed explanations in the **Prioritization** section above.
+ 1. If the affected version `xx.x` in `regression:xx.x` is older than the **current release**
+ 1. If the regression is an ~S1 severity, it's recommended to schedule the fix for the current milestone. We would like to fix the highest severity regression as soon as we can.
+ 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of the Engineering Manager and Product Manager.
## Release retrospective and kickoff
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index a9102743bf9..109e60cbde2 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -3,7 +3,7 @@
import $ from 'jquery';
import Vue from 'vue';
import Flash from '../../flash';
-import { __ } from '../../locale';
+import { sprintf, __ } from '../../locale';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
@@ -55,8 +55,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
return this.issue.labels && this.issue.labels.length;
},
labelDropdownTitle() {
- return this.hasLabels ?
- `${this.issue.labels[0].title} ${this.issue.labels.length - 1}+ more` : 'Label';
+ return this.hasLabels ? sprintf(__('%{firstLabel} +%{labelCount} more'), {
+ firstLabel: this.issue.labels[0].title,
+ labelCount: this.issue.labels.length - 1
+ }) : __('Label');
},
selectedLabels() {
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 050cbd8db48..ad473404c29 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -1,6 +1,7 @@
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len */
/* global ListIssue */
+import { __ } from '~/locale';
import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
import queryData from '../utils/query_data';
@@ -30,7 +31,7 @@ class List {
this.id = obj.id;
this._uid = this.guid();
this.position = obj.position;
- this.title = obj.title;
+ this.title = obj.list_type === 'backlog' ? __('Open') : obj.title;
this.type = obj.list_type;
const typeInfo = this.getTypeInfo(this.type);
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 589eeee9695..742cf490ad2 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -8,6 +8,7 @@ import 'core-js/fn/object/assign';
import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
+import 'core-js/fn/string/includes';
import 'core-js/fn/symbol';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 7cc4e6a2c3a..b5b05df4d34 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -114,11 +114,15 @@ export default {
this.adjustView();
},
methods: {
- ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles']),
+ ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles', 'startRenderDiffsQueue']),
fetchData() {
- this.fetchDiffFiles().catch(() => {
- createFlash(__('Something went wrong on our end. Please try again!'));
- });
+ this.fetchDiffFiles()
+ .then(() => {
+ requestIdleCallback(this.startRenderDiffsQueue, { timeout: 1000 });
+ })
+ .catch(() => {
+ createFlash(__('Something went wrong on our end. Please try again!'));
+ });
if (!this.isNotesFetched) {
eventHub.$emit('fetchNotesData');
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 7e7058d8d08..59e9ba08b8b 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -46,16 +46,25 @@ export default {
showExpandMessage() {
return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge;
},
+ showLoadingIcon() {
+ return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
+ },
},
methods: {
...mapActions('diffs', ['loadCollapsedDiff']),
handleToggle() {
const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file;
- if (collapsed && !highlightedDiffLines && !parallelDiffLines.length) {
+ if (
+ collapsed &&
+ !highlightedDiffLines &&
+ parallelDiffLines !== undefined &&
+ !parallelDiffLines.length
+ ) {
this.handleLoadCollapsedDiff();
} else {
this.file.collapsed = !this.file.collapsed;
+ this.file.renderIt = true;
}
},
handleLoadCollapsedDiff() {
@@ -65,6 +74,7 @@ export default {
.then(() => {
this.isLoadingCollapsedDiff = false;
this.file.collapsed = false;
+ this.file.renderIt = true;
})
.catch(() => {
this.isLoadingCollapsedDiff = false;
@@ -121,12 +131,12 @@ export default {
</div>
<diff-content
- v-if="!isCollapsed"
+ v-if="!isCollapsed && file.renderIt"
:class="{ hidden: isCollapsed || file.tooLarge }"
:diff-file="file"
/>
<loading-icon
- v-if="isLoadingCollapsedDiff"
+ v-else-if="showLoadingIcon"
class="diff-content loading"
/>
<div
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 2fa8367f528..f68afa44837 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -25,3 +25,6 @@ export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
export const UNFOLD_COUNT = 20;
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
export const LENGTH_OF_AVATAR_TOOLTIP = 17;
+
+export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
+export const MAX_LINES_TO_BE_RENDERED = 2000;
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 27001142257..4ab6ceb249a 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -29,6 +29,27 @@ export const fetchDiffFiles = ({ state, commit }) => {
.then(handleLocationHash);
};
+export const startRenderDiffsQueue = ({ state, commit }) => {
+ const checkItem = () => {
+ const nextFile = state.diffFiles.find(
+ file => !file.renderIt && (!file.collapsed || !file.text),
+ );
+ if (nextFile) {
+ requestAnimationFrame(() => {
+ commit(types.RENDER_FILE, nextFile);
+ });
+ requestIdleCallback(
+ () => {
+ checkItem();
+ },
+ { timeout: 1000 },
+ );
+ }
+ };
+
+ checkItem();
+};
+
export const setInlineDiffViewType = ({ commit }) => {
commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE);
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 2c8e1a1466f..c999d637d50 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -8,3 +8,4 @@ export const REMOVE_COMMENT_FORM_LINE = 'REMOVE_COMMENT_FORM_LINE';
export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS';
export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES';
+export const RENDER_FILE = 'RENDER_FILE';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index a98b2be89a3..0522e32c410 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { findDiffFile, addLineReferences, removeMatchLine, addContextLines } from './utils';
+import { LINES_TO_BE_RENDERED_DIRECTLY, MAX_LINES_TO_BE_RENDERED } from '../constants';
import * as types from './mutation_types';
export default {
@@ -15,8 +16,48 @@ export default {
},
[types.SET_DIFF_DATA](state, data) {
+ const diffData = convertObjectPropsToCamelCase(data, { deep: true });
+ let showingLines = 0;
+ const filesLength = diffData.diffFiles.length;
+ let i;
+ for (i = 0; i < filesLength; i += 1) {
+ const file = diffData.diffFiles[i];
+ if (file.parallelDiffLines) {
+ const linesLength = file.parallelDiffLines.length;
+ let u = 0;
+ for (u = 0; u < linesLength; u += 1) {
+ const line = file.parallelDiffLines[u];
+ if (line.left) delete line.left.text;
+ if (line.right) delete line.right.text;
+ }
+ }
+
+ if (file.highlightedDiffLines) {
+ const linesLength = file.highlightedDiffLines.length;
+ let u;
+ for (u = 0; u < linesLength; u += 1) {
+ const line = file.highlightedDiffLines[u];
+ delete line.text;
+ }
+ }
+
+ if (file.highlightedDiffLines) {
+ showingLines += file.parallelDiffLines.length;
+ }
+ Object.assign(file, {
+ renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
+ collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
+ });
+ }
+
Object.assign(state, {
- ...convertObjectPropsToCamelCase(data, { deep: true }),
+ ...diffData,
+ });
+ },
+
+ [types.RENDER_FILE](state, file) {
+ Object.assign(file, {
+ renderIt: true,
});
},
diff --git a/app/assets/javascripts/emoji/support/unicode_support_map.js b/app/assets/javascripts/emoji/support/unicode_support_map.js
index 8c1861c56db..651169391fe 100644
--- a/app/assets/javascripts/emoji/support/unicode_support_map.js
+++ b/app/assets/javascripts/emoji/support/unicode_support_map.js
@@ -86,7 +86,7 @@ function generateUnicodeSupportMap(testMap) {
canvas.height = numTestEntries * fontSize;
ctx.fillStyle = '#000000';
ctx.textBaseline = 'middle';
- ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
+ ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`;
// Write each emoji to the canvas vertically
let writeIndex = 0;
testMapKeys.forEach(testKey => {
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 5611b37be7c..00ae5ea2c15 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -61,7 +61,7 @@ export default {
<slot name="header"></slot>
</header>
<div
- class="ide-tree-body"
+ class="ide-tree-body h-100"
>
<repo-file
v-for="file in currentTree.tree"
diff --git a/app/assets/javascripts/ide/components/new_dropdown/button.vue b/app/assets/javascripts/ide/components/new_dropdown/button.vue
index ff114e47741..aa5fce59dbf 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/button.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/button.vue
@@ -1,7 +1,11 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
export default {
+ directives: {
+ tooltip,
+ },
components: {
Icon,
},
@@ -26,6 +30,11 @@ export default {
default: true,
},
},
+ computed: {
+ tooltipTitle() {
+ return this.showLabel ? '' : this.label;
+ },
+ },
methods: {
clicked() {
this.$emit('click');
@@ -36,7 +45,9 @@ export default {
<template>
<button
+ v-tooltip
:aria-label="label"
+ :title="tooltipTitle"
type="button"
class="btn-blank"
@click.stop.prevent="clicked"
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index fef36eae7b1..39a1bd1f61b 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -2,6 +2,7 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import { Manager } from 'smooshpack';
+import { listen } from 'codesandbox-api';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Navigator from './navigator.vue';
import { packageJsonPath } from '../../constants';
@@ -16,6 +17,7 @@ export default {
return {
manager: {},
loading: false,
+ sandpackReady: false,
};
},
computed: {
@@ -81,6 +83,10 @@ export default {
}
this.manager = {};
+ if (this.listener) {
+ this.listener();
+ }
+
clearTimeout(this.timeout);
this.timeout = null;
},
@@ -96,17 +102,29 @@ export default {
return this.loadFileContent(this.mainEntry)
.then(() => this.$nextTick())
- .then(() =>
+ .then(() => {
this.initManager('#ide-preview', this.sandboxOpts, {
fileResolver: {
isFile: p => Promise.resolve(!!this.entries[createPathWithExt(p)]),
readFile: p => this.loadFileContent(createPathWithExt(p)).then(content => content),
},
- }),
- );
+ });
+
+ this.listener = listen(e => {
+ switch (e.type) {
+ case 'done':
+ this.sandpackReady = true;
+ break;
+ default:
+ break;
+ }
+ });
+ });
},
update() {
- if (this.timeout) return;
+ if (!this.sandpackReady) return;
+
+ clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
if (_.isEmpty(this.manager)) {
@@ -116,10 +134,7 @@ export default {
}
this.manager.updatePreview(this.sandboxOpts);
-
- clearTimeout(this.timeout);
- this.timeout = null;
- }, 500);
+ }, 250);
},
initManager(el, opts, resolver) {
this.manager = new Manager(el, opts, resolver);
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index c6d7d218e81..3f6101e58f4 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -3,7 +3,6 @@ import VueRouter from 'vue-router';
import { join as joinPath } from 'path';
import flash from '~/flash';
import store from './stores';
-import { activityBarViews } from './constants';
Vue.use(VueRouter);
@@ -74,98 +73,23 @@ router.beforeEach((to, from, next) => {
projectId: to.params.project,
})
.then(() => {
- const fullProjectId = `${to.params.namespace}/${to.params.project}`;
-
+ const basePath = to.params[0] || '';
+ const projectId = `${to.params.namespace}/${to.params.project}`;
const branchId = to.params.branchid;
+ const mergeRequestId = to.params.mrid;
if (branchId) {
- const basePath = to.params[0] || '';
-
- store.dispatch('setCurrentBranchId', branchId);
-
- store.dispatch('getBranchData', {
- projectId: fullProjectId,
+ store.dispatch('openBranch', {
+ projectId,
branchId,
+ basePath,
+ });
+ } else if (mergeRequestId) {
+ store.dispatch('openMergeRequest', {
+ projectId,
+ mergeRequestId,
+ targetProjectId: to.query.target_project,
});
-
- store
- .dispatch('getFiles', {
- projectId: fullProjectId,
- branchId,
- })
- .then(() => {
- if (basePath) {
- const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
- const treeEntryKey = Object.keys(store.state.entries).find(
- key => key === path && !store.state.entries[key].pending,
- );
- const treeEntry = store.state.entries[treeEntryKey];
-
- if (treeEntry) {
- store.dispatch('handleTreeEntryAction', treeEntry);
- }
- }
- })
- .catch(e => {
- throw e;
- });
- } else if (to.params.mrid) {
- store
- .dispatch('getMergeRequestData', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- })
- .then(mr => {
- store.dispatch('updateActivityBarView', activityBarViews.review);
-
- store.dispatch('getBranchData', {
- projectId: fullProjectId,
- branchId: mr.source_branch,
- });
-
- return store.dispatch('getFiles', {
- projectId: fullProjectId,
- branchId: mr.source_branch,
- });
- })
- .then(() =>
- store.dispatch('getMergeRequestVersions', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- }),
- )
- .then(() =>
- store.dispatch('getMergeRequestChanges', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- }),
- )
- .then(mrChanges => {
- mrChanges.changes.forEach((change, ind) => {
- const changeTreeEntry = store.state.entries[change.new_path];
-
- if (changeTreeEntry) {
- store.dispatch('setFileMrChange', {
- file: changeTreeEntry,
- mrChange: change,
- });
-
- if (ind < 10) {
- store.dispatch('getFileData', {
- path: change.new_path,
- makeFileActive: ind === 0,
- });
- }
- }
- });
- })
- .catch(e => {
- flash('Error while loading the merge request. Please try again.');
- throw e;
- });
}
})
.catch(e => {
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 9e3f5da4676..c9795750d65 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -54,9 +54,6 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
commit(types.SET_FILE_ACTIVE, { path, active: true });
dispatch('scrollToTab');
-
- commit(types.SET_CURRENT_PROJECT, file.projectId);
- commit(types.SET_CURRENT_BRANCH, file.branchId);
};
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index 1887b77b00b..187f8c75d07 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -1,6 +1,8 @@
-import { __ } from '../../../locale';
+import flash from '~/flash';
+import { __ } from '~/locale';
import service from '../../services';
import * as types from '../mutation_types';
+import { activityBarViews } from '../../constants';
export const getMergeRequestData = (
{ commit, dispatch, state },
@@ -104,3 +106,67 @@ export const getMergeRequestVersions = (
resolve(state.projects[projectId].mergeRequests[mergeRequestId].versions);
}
});
+
+export const openMergeRequest = (
+ { dispatch, state },
+ { projectId, targetProjectId, mergeRequestId } = {},
+) =>
+ dispatch('getMergeRequestData', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ })
+ .then(mr => {
+ dispatch('setCurrentBranchId', mr.source_branch);
+
+ dispatch('getBranchData', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+
+ return dispatch('getFiles', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+ })
+ .then(() =>
+ dispatch('getMergeRequestVersions', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(() =>
+ dispatch('getMergeRequestChanges', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(mrChanges => {
+ if (mrChanges.changes.length) {
+ dispatch('updateActivityBarView', activityBarViews.review);
+ }
+
+ mrChanges.changes.forEach((change, ind) => {
+ const changeTreeEntry = state.entries[change.new_path];
+
+ if (changeTreeEntry) {
+ dispatch('setFileMrChange', {
+ file: changeTreeEntry,
+ mrChange: change,
+ });
+
+ if (ind < 10) {
+ dispatch('getFileData', {
+ path: change.new_path,
+ makeFileActive: ind === 0,
+ });
+ }
+ }
+ });
+ })
+ .catch(e => {
+ flash(__('Error while loading the merge request. Please try again.'));
+ throw e;
+ });
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 501e25d452b..543dc6c0461 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -124,3 +124,35 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
actionPayload: branchId,
});
};
+
+export const openBranch = (
+ { dispatch, state },
+ { projectId, branchId, basePath },
+) => {
+ dispatch('setCurrentBranchId', branchId);
+
+ dispatch('getBranchData', {
+ projectId,
+ branchId,
+ });
+
+ return (
+ dispatch('getFiles', {
+ projectId,
+ branchId,
+ })
+ .then(() => {
+ if (basePath) {
+ const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
+ const treeEntryKey = Object.keys(state.entries).find(
+ key => key === path && !state.entries[key].pending,
+ );
+ const treeEntry = state.entries[treeEntryKey];
+
+ if (treeEntry) {
+ dispatch('handleTreeEntryAction', treeEntry);
+ }
+ }
+ })
+ );
+};
diff --git a/app/assets/javascripts/ide/stores/modules/branches/actions.js b/app/assets/javascripts/ide/stores/modules/branches/actions.js
index 74aa98ef9f9..f90c2d77f2b 100644
--- a/app/assets/javascripts/ide/stores/modules/branches/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/branches/actions.js
@@ -33,7 +33,4 @@ export const fetchBranches = ({ dispatch, rootGetters }, { search = '' }) => {
export const resetBranches = ({ commit }) => commit(types.RESET_BRANCHES);
-export const openBranch = ({ rootState, dispatch }, id) =>
- dispatch('goToRoute', `/project/${rootState.currentProjectId}/edit/${id}`, { root: true });
-
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 1eda5768709..56a8d9430c7 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -200,6 +200,7 @@ export default {
},
[types.DELETE_ENTRY](state, path) {
const entry = state.entries[path];
+ const { tempFile = false } = entry;
const parent = entry.parentPath
? state.entries[entry.parentPath]
: state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
@@ -209,7 +210,11 @@ export default {
parent.tree = parent.tree.filter(f => f.path !== entry.path);
if (entry.type === 'blob') {
- state.changedFiles = state.changedFiles.concat(entry);
+ if (tempFile) {
+ state.changedFiles = state.changedFiles.filter(f => f.path !== path);
+ } else {
+ state.changedFiles = state.changedFiles.concat(entry);
+ }
}
},
[types.RENAME_ENTRY](state, { path, name, entryPath = null }) {
diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue
new file mode 100644
index 00000000000..525c5eec91a
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/artifacts_block.vue
@@ -0,0 +1,98 @@
+<script>
+ import TimeagoTooltiop from '~/vue_shared/components/time_ago_tooltip.vue';
+
+ export default {
+ components: {
+ TimeagoTooltiop,
+ },
+ props: {
+ // @build.artifacts_expired?
+ haveArtifactsExpired: {
+ type: Boolean,
+ required: true,
+ },
+ // @build.has_expiring_artifacts?
+ willArtifactsExpire: {
+ type: Boolean,
+ required: true,
+ },
+ expireAt: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ keepArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ downloadArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ browseArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ };
+</script>
+<template>
+ <div class="block">
+ <div class="title">
+ {{ s__('Job|Job artifacts') }}
+ </div>
+
+ <p
+ v-if="haveArtifactsExpired"
+ class="js-artifacts-removed build-detail-row"
+ >
+ {{ s__('Job|The artifacts were removed') }}
+ </p>
+ <p
+ v-else-if="willArtifactsExpire"
+ class="js-artifacts-will-be-removed build-detail-row"
+ >
+ {{ s__('Job|The artifacts will be removed') }}
+ </p>
+
+ <timeago-tooltiop
+ v-if="expireAt"
+ :time="expireAt"
+ />
+
+ <div
+ class="btn-group d-flex"
+ role="group"
+ >
+ <a
+ v-if="keepArtifactsPath"
+ :href="keepArtifactsPath"
+ class="js-keep-artifacts btn btn-sm btn-default"
+ data-method="post"
+ >
+ {{ s__('Job|Keep') }}
+ </a>
+
+ <a
+ v-if="downloadArtifactsPath"
+ :href="downloadArtifactsPath"
+ class="js-download-artifacts btn btn-sm btn-default"
+ download
+ rel="nofollow"
+ >
+ {{ s__('Job|Download') }}
+ </a>
+
+ <a
+ v-if="browseArtifactsPath"
+ :href="browseArtifactsPath"
+ class="js-browse-artifacts btn btn-sm btn-default"
+ >
+ {{ s__('Job|Browse') }}
+ </a>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue
new file mode 100644
index 00000000000..d688eebfa95
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/erased_block.vue
@@ -0,0 +1,48 @@
+<script>
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ components: {
+ TimeagoTooltip,
+ },
+ props: {
+ erasedByUser: {
+ type: Boolean,
+ required: true,
+ },
+ username: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ linkToUser: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ erasedAt: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <div class="prepend-top-default js-build-erased">
+ <div class="erased alert alert-warning">
+ <template v-if="erasedByUser">
+ {{ s__("Job|Job has been erased by") }}
+ <a :href="linkToUser">
+ {{ username }}
+ </a>
+ </template>
+ <template v-else>
+ {{ s__("Job|Job has been erased") }}
+ </template>
+
+ <timeago-tooltip
+ :time="erasedAt"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
new file mode 100644
index 00000000000..3c4749d996b
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -0,0 +1,33 @@
+<script>
+ export default {
+ name: 'JobLog',
+ props: {
+ trace: {
+ type: String,
+ required: true,
+ },
+ isReceivingBuildTrace: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ };
+</script>
+<template>
+ <pre class="build-trace">
+ <code
+ class="bash"
+ v-html="trace"
+ >
+ </code>
+
+ <div
+ v-if="isReceivingBuildTrace"
+ class="js-log-animation build-loader-animation"
+ >
+ <div class="dot"></div>
+ <div class="dot"></div>
+ <div class="dot"></div>
+ </div>
+ </pre>
+</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index d2adf628050..36d4a3e2bc9 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -1,14 +1,16 @@
<script>
-import detailRow from './sidebar_detail_row.vue';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import timeagoMixin from '../../vue_shared/mixins/timeago';
-import { timeIntervalInWords } from '../../lib/utils/datetime_utility';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
+import Icon from '~/vue_shared/components/icon.vue';
+import DetailRow from './sidebar_detail_row.vue';
export default {
name: 'SidebarDetailsBlock',
components: {
- detailRow,
- loadingIcon,
+ DetailRow,
+ LoadingIcon,
+ Icon,
},
mixins: [timeagoMixin],
props: {
@@ -20,16 +22,16 @@ export default {
type: Boolean,
required: true,
},
- canUserRetry: {
- type: Boolean,
- required: false,
- default: false,
- },
runnerHelpUrl: {
type: String,
required: false,
default: '',
},
+ terminalPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
shouldRenderContent() {
@@ -92,7 +94,7 @@ export default {
{{ job.name }}
</strong>
<a
- v-if="canUserRetry"
+ v-if="job.retry_path"
:class="retryButtonClass"
:href="job.retry_path"
data-method="post"
@@ -100,6 +102,16 @@ export default {
>
{{ __('Retry') }}
</a>
+ <a
+ v-if="terminalPath"
+ :href="terminalPath"
+ class="js-terminal-link pull-right btn btn-primary
+ btn-inverted visible-md-block visible-lg-block"
+ target="_blank"
+ >
+ {{ __('Debug') }}
+ <icon name="external-link" />
+ </a>
<button
:aria-label="__('Toggle Sidebar')"
type="button"
@@ -125,7 +137,7 @@ export default {
{{ __('New issue') }}
</a>
<a
- v-if="canUserRetry"
+ v-if="job.retry_path"
:href="job.retry_path"
class="js-retry-job btn btn-inverted-secondary"
data-method="post"
diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue
new file mode 100644
index 00000000000..18883fea950
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/stuck_block.vue
@@ -0,0 +1,63 @@
+<script>
+/**
+ * Renders Stuck Runners block for job's view.
+ */
+export default {
+ props: {
+ hasNoRunnersForProject: {
+ type: Boolean,
+ required: true,
+ },
+ tags: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ runnersPath: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <div class="bs-callout bs-callout-warning">
+ <p
+ v-if="hasNoRunnersForProject"
+ class="js-stuck-no-runners"
+ >
+ {{ s__(`Job|This job is stuck, because the project
+ doesn't have any runners online assigned to it.`) }}
+ </p>
+ <p
+ v-else-if="tags.length"
+ class="js-stuck-with-tags"
+ >
+ {{ s__(`This job is stuck, because you don't have
+ any active runners online with any of these tags assigned to them:`) }}
+ <span
+ v-for="(tag, index) in tags"
+ :key="index"
+ class="badge badge-primary"
+ >
+ {{ tag }}
+ </span>
+ </p>
+ <p
+ v-else
+ class="js-stuck-no-active-runner"
+ >
+ {{ s__(`This job is stuck, because you don't
+ have any active runners that can run this job.`) }}
+ </p>
+
+ {{ __("Go to") }}
+ <a
+ v-if="runnersPath"
+ :href="runnersPath"
+ class="js-runners-path"
+ >
+ {{ __("Runners page") }}
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index 0db7b95636c..a84324f14b2 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -52,9 +52,9 @@ export default () => {
return createElement('details-block', {
props: {
isLoading: this.mediator.state.isLoading,
- canUserRetry: !!('canUserRetry' in detailsBlockDataset),
job: this.mediator.store.state.job,
runnerHelpUrl: dataset.runnerHelpUrl,
+ terminalPath: detailsBlockDataset.terminalPath,
},
});
},
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index cb851ff6745..6499b919787 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -4,7 +4,7 @@
import $ from 'jquery';
import _ from 'underscore';
-import { __ } from './locale';
+import { sprintf, __ } from './locale';
import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils';
@@ -39,7 +39,7 @@ export default class LabelsSelect {
showNo = $dropdown.data('showNo');
showAny = $dropdown.data('showAny');
showMenuAbove = $dropdown.data('showMenuAbove');
- defaultLabel = $dropdown.data('defaultLabel') || 'Label';
+ defaultLabel = $dropdown.data('defaultLabel') || __('Label');
abilityName = $dropdown.data('abilityName');
$selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block');
@@ -267,7 +267,10 @@ export default class LabelsSelect {
return selectedLabels;
}
else if (selectedLabels.length) {
- return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more";
+ return sprintf(__('%{firstLabel} +%{labelCount} more'), {
+ firstLabel: selectedLabels[0],
+ labelCount: selectedLabels.length - 1
+ });
}
else {
return defaultLabel;
diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js
index 48d75f5443b..47bd70537f1 100644
--- a/app/assets/javascripts/pages/admin/application_settings/index.js
+++ b/app/assets/javascripts/pages/admin/application_settings/index.js
@@ -1,6 +1,8 @@
import initSettingsPanels from '~/settings_panels';
+import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
+ projectSelect();
});
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
index ffc84dc106b..78cf5406e43 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -1,3 +1,9 @@
import initForm from '../form';
+import MirrorRepos from './mirror_repos';
-document.addEventListener('DOMContentLoaded', initForm);
+document.addEventListener('DOMContentLoaded', () => {
+ initForm();
+
+ const mirrorReposContainer = document.querySelector('.js-mirror-settings');
+ if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init();
+});
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js
new file mode 100644
index 00000000000..4c56af20cc3
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js
@@ -0,0 +1,94 @@
+import $ from 'jquery';
+import _ from 'underscore';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+
+export default class MirrorRepos {
+ constructor(container) {
+ this.$container = $(container);
+ this.$form = $('.js-mirror-form', this.$container);
+ this.$urlInput = $('.js-mirror-url', this.$form);
+ this.$protectedBranchesInput = $('.js-mirror-protected', this.$form);
+ this.$table = $('.js-mirrors-table-body', this.$container);
+ this.mirrorEndpoint = this.$form.data('projectMirrorEndpoint');
+ }
+
+ init() {
+ this.initMirrorPush();
+ this.registerUpdateListeners();
+ }
+
+ initMirrorPush() {
+ this.$passwordGroup = $('.js-password-group', this.$container);
+ this.$password = $('.js-password', this.$passwordGroup);
+ this.$authMethod = $('.js-auth-method', this.$form);
+
+ this.$authMethod.on('change', () => this.togglePassword());
+ this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl());
+ }
+
+ updateUrl() {
+ let val = this.$urlInput.val();
+
+ if (this.$password) {
+ const password = this.$password.val();
+ if (password) val = val.replace('@', `:${password}@`);
+ }
+
+ $('.js-mirror-url-hidden', this.$form).val(val);
+ }
+
+ updateProtectedBranches() {
+ const val = this.$protectedBranchesInput.get(0).checked
+ ? this.$protectedBranchesInput.val()
+ : '0';
+ $('.js-mirror-protected-hidden', this.$form).val(val);
+ }
+
+ registerUpdateListeners() {
+ this.debouncedUpdateUrl = _.debounce(() => this.updateUrl(), 200);
+ this.$urlInput.on('input', () => this.debouncedUpdateUrl());
+ this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches());
+ this.$table.on('click', '.js-delete-mirror', event => this.deleteMirror(event));
+ }
+
+ togglePassword() {
+ const isPassword = this.$authMethod.val() === 'password';
+
+ if (!isPassword) {
+ this.$password.val('');
+ this.updateUrl();
+ }
+ this.$passwordGroup.collapse(isPassword ? 'show' : 'hide');
+ }
+
+ deleteMirror(event, existingPayload) {
+ const $target = $(event.currentTarget);
+ let payload = existingPayload;
+
+ if (!payload) {
+ payload = {
+ project: {
+ remote_mirrors_attributes: {
+ id: $target.data('mirrorId'),
+ enabled: 0,
+ },
+ },
+ };
+ }
+
+ return axios
+ .put(this.mirrorEndpoint, payload)
+ .then(() => this.removeRow($target))
+ .catch(() => Flash(__('Failed to remove mirror.')));
+ }
+
+ /* eslint-disable class-methods-use-this */
+ removeRow($target) {
+ const row = $target.closest('tr');
+ $('.js-delete-mirror', row).tooltip('hide');
+ row.remove();
+ }
+ /* eslint-enable class-methods-use-this */
+}
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index bce7556bd40..6f3b32f8eea 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -14,6 +14,7 @@ export default function projectSelect() {
this.orderBy = $(select).data('orderBy') || 'id';
this.withIssuesEnabled = $(select).data('withIssuesEnabled');
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
+ this.allowClear = $(select).data('allowClear') || false;
placeholder = "Search for project";
if (this.includeGroups) {
@@ -71,6 +72,13 @@ export default function projectSelect() {
text: function (project) {
return project.name_with_namespace || project.name;
},
+
+ initSelection: function(el, callback) {
+ return Api.project(el.val()).then(({ data }) => callback(data));
+ },
+
+ allowClear: this.allowClear,
+
dropdownCssClass: "ajax-project-dropdown"
});
if (simpleFilter) return select;
diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
index 140475b4dfa..7b37f4e9a97 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -1,10 +1,10 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { s__ } from '~/locale';
- import { componentNames } from '~/vue_shared/components/reports/issue_body';
- import ReportSection from '~/vue_shared/components/reports/report_section.vue';
- import SummaryRow from '~/vue_shared/components/reports/summary_row.vue';
- import IssuesList from '~/vue_shared/components/reports/issues_list.vue';
+ import { componentNames } from './issue_body';
+ import ReportSection from './report_section.vue';
+ import SummaryRow from './summary_row.vue';
+ import IssuesList from './issues_list.vue';
import Modal from './modal.vue';
import createStore from '../store';
import { summaryTextBuilder, reportTextBuilder, statusIcon } from '../store/utils';
diff --git a/app/assets/javascripts/vue_shared/components/reports/issue_body.js b/app/assets/javascripts/reports/components/issue_body.js
index 54dfb7b16bf..8b5af263d50 100644
--- a/app/assets/javascripts/vue_shared/components/reports/issue_body.js
+++ b/app/assets/javascripts/reports/components/issue_body.js
@@ -1,4 +1,4 @@
-import TestIssueBody from '~/reports/components/test_issue_body.vue';
+import TestIssueBody from './test_issue_body.vue';
export const components = {
TestIssueBody,
diff --git a/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue b/app/assets/javascripts/reports/components/issue_status_icon.vue
index f8189117ac3..85811698a37 100644
--- a/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue
+++ b/app/assets/javascripts/reports/components/issue_status_icon.vue
@@ -1,11 +1,10 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
-
import {
STATUS_FAILED,
STATUS_NEUTRAL,
STATUS_SUCCESS,
-} from '~/vue_shared/components/reports/constants';
+} from '../constants';
export default {
name: 'IssueStatusIcon',
diff --git a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue
index 2545e84f932..df42201b5de 100644
--- a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue
+++ b/app/assets/javascripts/reports/components/issues_list.vue
@@ -1,10 +1,10 @@
<script>
-import IssuesBlock from '~/vue_shared/components/reports/report_issues.vue';
+import IssuesBlock from '~/reports/components/report_issues.vue';
import {
STATUS_SUCCESS,
STATUS_FAILED,
STATUS_NEUTRAL,
-} from '~/vue_shared/components/reports/constants';
+} from '~/reports/constants';
/**
* Renders block of issues
diff --git a/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue b/app/assets/javascripts/reports/components/modal_open_name.vue
index 4f81cee2a38..4f81cee2a38 100644
--- a/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue
+++ b/app/assets/javascripts/reports/components/modal_open_name.vue
diff --git a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue b/app/assets/javascripts/reports/components/report_issues.vue
index 1f13e555b31..c553a374f66 100644
--- a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue
+++ b/app/assets/javascripts/reports/components/report_issues.vue
@@ -1,6 +1,6 @@
<script>
-import IssueStatusIcon from '~/vue_shared/components/reports/issue_status_icon.vue';
-import { components, componentNames } from '~/vue_shared/components/reports/issue_body';
+import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
+import { components, componentNames } from '~/reports/components/issue_body';
export default {
name: 'ReportIssues',
diff --git a/app/assets/javascripts/vue_shared/components/reports/report_link.vue b/app/assets/javascripts/reports/components/report_link.vue
index 74d68f9f439..74d68f9f439 100644
--- a/app/assets/javascripts/vue_shared/components/reports/report_link.vue
+++ b/app/assets/javascripts/reports/components/report_link.vue
diff --git a/app/assets/javascripts/vue_shared/components/reports/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index a6dbf21092b..dc609d6f90e 100644
--- a/app/assets/javascripts/vue_shared/components/reports/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -1,8 +1,8 @@
<script>
import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
+import Popover from '~/vue_shared/components/help_popover.vue';
import IssuesList from './issues_list.vue';
-import Popover from '../help_popover.vue';
const LOADING = 'LOADING';
const ERROR = 'ERROR';
diff --git a/app/assets/javascripts/vue_shared/components/reports/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue
index 063beab58fc..4456d84c968 100644
--- a/app/assets/javascripts/vue_shared/components/reports/summary_row.vue
+++ b/app/assets/javascripts/reports/components/summary_row.vue
@@ -1,7 +1,7 @@
<script>
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
-import Popover from '../help_popover.vue';
+import Popover from '~/vue_shared/components/help_popover.vue';
/**
* Renders the summary row for each report
diff --git a/app/assets/javascripts/reports/constants.js b/app/assets/javascripts/reports/constants.js
index 807ecb1039e..c323dc543f3 100644
--- a/app/assets/javascripts/reports/constants.js
+++ b/app/assets/javascripts/reports/constants.js
@@ -11,6 +11,8 @@ export const SUCCESS = 'SUCCESS';
export const STATUS_FAILED = 'failed';
export const STATUS_SUCCESS = 'success';
+export const STATUS_NEUTRAL = 'neutral';
+
export const ICON_WARNING = 'warning';
export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound';
diff --git a/app/assets/javascripts/vue_shared/components/reports/constants.js b/app/assets/javascripts/vue_shared/components/reports/constants.js
deleted file mode 100644
index dbde648bfdb..00000000000
--- a/app/assets/javascripts/vue_shared/components/reports/constants.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const STATUS_FAILED = 'failed';
-export const STATUS_SUCCESS = 'success';
-export const STATUS_NEUTRAL = 'neutral';
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index c20738a20c3..e8e707cf90c 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -14,7 +14,6 @@ $border-radius-base: 3px !default;
$modal-body-bg: $white-light;
$input-border: $border-color;
-$input-border-focus: $focus-border-color;
$padding-base-vertical: $gl-vert-padding;
$padding-base-horizontal: $gl-padding;
@@ -86,7 +85,7 @@ strong {
}
a {
- color: $gl-link-color;
+ color: $blue-600;
}
hr {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c46b0b5db09..b1a20c06910 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -1,4 +1,5 @@
@import 'framework/variables';
+@import 'framework/variables_overrides';
@import 'framework/mixins';
@import 'bootstrap';
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 369556dc24e..4c7c399a3ca 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -103,6 +103,7 @@
display: flex;
a {
+ width: 100%;
display: flex;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 646cedd79ed..0dc7aa4ef68 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -350,7 +350,7 @@
&:focus {
cursor: text;
box-shadow: none;
- border-color: lighten($dropdown-input-focus-border, 20%);
+ border-color: lighten($blue-300, 20%);
color: $gray-darkest;
background-color: $gray-light;
}
@@ -434,7 +434,7 @@
&:hover,
&:active,
&:focus {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: none;
}
}
@@ -445,7 +445,7 @@
&:hover,
&:active,
&:focus {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index af17210f341..48a87ea8616 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -114,7 +114,11 @@ hr {
.item-title { font-weight: $gl-font-weight-bold; }
.author-link {
- color: $gl-link-color;
+ color: $blue-600;
+}
+
+.author-link:hover {
+ text-decoration: none;
}
.back-link {
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index eebce8b9011..83bc3776178 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -615,7 +615,7 @@
&:focus {
color: $dropdown-link-color;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
box-shadow: 0 0 4px $dropdown-input-focus-shadow;
~ .fa {
diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss
index 3cde0490371..a8ec1e1145a 100644
--- a/app/assets/stylesheets/framework/emojis.scss
+++ b/app/assets/stylesheets/framework/emojis.scss
@@ -2,7 +2,7 @@ gl-emoji {
font-style: normal;
display: inline-flex;
vertical-align: middle;
- font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1.5em;
line-height: 0.9;
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 00eac1688f2..54882633fea 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -286,19 +286,19 @@ span.idiff {
.new-file {
a {
- color: $gl-text-green;
+ color: $green-600;
}
}
.renamed-file {
a {
- color: $gl-text-orange;
+ color: $orange-600;
}
}
.deleted-file {
a {
- color: $gl-text-red;
+ color: $red-500;
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 5d79610b21e..9b09ed0ed0a 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -205,7 +205,7 @@
&.focus,
&.focus:hover {
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
box-shadow: 0 0 4px $search-input-focus-shadow-color;
}
@@ -294,7 +294,7 @@
&:hover,
&:focus {
color: $gl-text-color;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
outline: none;
}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index e4bcb92876d..7a4c3914fb0 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -16,10 +16,10 @@
color: $gl-text-color;
a {
- color: $gl-link-color;
+ color: $blue-600;
&:hover {
- color: $gl-link-hover-color;
+ color: $blue-800;
text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index d7149d93622..a70eece8f68 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -170,7 +170,7 @@ label {
}
.form-control::-webkit-input-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
.input-group {
@@ -201,7 +201,7 @@ label {
}
.gl-show-field-errors {
- .form-control {
+ .form-control:not(textarea) {
height: 34px;
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 7290a174668..d8391b59a8c 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -179,7 +179,7 @@
&:hover,
&:focus {
svg {
- fill: $gl-link-color;
+ fill: $blue-600;
}
}
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index ffb40166c15..7d53a631cdf 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -79,7 +79,6 @@ body.modal-open {
.modal {
background-color: $black-transparent;
- z-index: 2100;
@include media-breakpoint-up(md) {
.modal-dialog {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b40dcf93969..88d2f0aaf85 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -153,7 +153,7 @@
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus {
- border-color: $input-border-focus;
+ border-color: $blue-300;
}
&.select2-active {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 473ca408c04..eccc814b747 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -180,7 +180,7 @@
}
a > code {
- color: $gl-link-color;
+ color: $blue-600;
}
dd {
@@ -423,25 +423,25 @@ h4 {
input,
textarea {
&::-webkit-input-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
// support firefox 19+ vendor prefix
&::-moz-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
opacity: 1; // FF defaults to 0.54
}
// scss-lint:disable PseudoElement
// support Edge vendor prefix
&::-ms-input-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
// scss-lint:disable PseudoElement
// support IE vendor prefix
&:-ms-input-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4db9efff6ee..866cb88ba5b 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -177,7 +177,6 @@ $border-gray-dark: darken($white-normal, $darken-border-factor);
* UI elements
*/
$border-color: #e5e5e5;
-$focus-border-color: $blue-300;
$well-expand-item: #e8f2f7;
$well-inner-border: #eef0f2;
$well-light-border: #f1f1f1;
@@ -196,19 +195,10 @@ $gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85);
$gl-text-color-disabled: #919191;
-$gl-text-green: $green-600;
-$gl-text-green-hover: $green-700;
-$gl-text-red: $red-500;
-$gl-text-orange: $orange-600;
-$gl-link-color: $blue-600;
-$gl-link-hover-color: $blue-800;
$gl-grayish-blue: #7f8fa4;
-$gl-gray: $gl-text-color;
$gl-gray-dark: #313236;
$gl-gray-light: #5c5c5c;
$gl-header-color: #4c4e54;
-$gl-header-nav-hover-color: #434343;
-$placeholder-text-color: $gl-text-color-tertiary;
/*
* Lists
@@ -227,7 +217,7 @@ $list-warning-row-color: $orange-700;
/*
* Markdown
*/
-$md-link-color: $gl-link-color;
+$md-link-color: $blue-600;
$md-area-border: #ddd;
/*
@@ -374,7 +364,7 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
$monospace-font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono',
'Courier New', 'andale mono', 'lucida console', monospace;
$regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
- 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+ 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/*
* Dropdowns
@@ -392,8 +382,7 @@ $dropdown-divider-color: rgba(#000, 0.1);
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555;
$dropdown-input-fa-color: #c7c7c7;
-$dropdown-input-focus-border: $focus-border-color;
-$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, 0.4);
+$dropdown-input-focus-shadow: rgba($blue-300, 0.4);
$dropdown-loading-bg: rgba(#fff, 0.6);
$dropdown-chevron-size: 10px;
$dropdown-toggle-active-border-color: darken($border-color, 14%);
@@ -585,8 +574,8 @@ $commit-message-text-area-bg: rgba(0, 0, 0, 0);
$common-gray: $gl-text-color;
$common-gray-light: #bbb;
$common-gray-dark: #444;
-$common-red: $gl-text-red;
-$common-green: $gl-text-green;
+$common-red: $red-500;
+$common-green: $green-600;
/*
* Editor
@@ -834,19 +823,3 @@ Prometheus
$prometheus-table-row-highlight-color: $theme-gray-100;
$priority-label-empty-state-width: 114px;
-
-/*
- * Override Bootstrap 4 variables
- */
-
-$secondary: $gray-light;
-$input-disabled-bg: $gray-light;
-$input-border-color: $theme-gray-200;
-$input-color: $gl-text-color;
-$font-family-sans-serif: $regular-font;
-$font-family-monospace: $monospace-font;
-$input-line-height: 20px;
-$btn-line-height: 20px;
-$table-accent-bg: $gray-light;
-$card-border-color: $border-color;
-$card-cap-bg: $gray-light;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
new file mode 100644
index 00000000000..b9c343fa2e9
--- /dev/null
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -0,0 +1,16 @@
+/*
+ * This file is only for overriding Bootstrap 4 variables.
+ * Please add any new variables to variables.scss
+ */
+
+$secondary: $gray-light;
+$input-disabled-bg: $gray-light;
+$input-border-color: $theme-gray-200;
+$input-color: $gl-text-color;
+$font-family-sans-serif: $regular-font;
+$font-family-monospace: $monospace-font;
+$input-line-height: 20px;
+$btn-line-height: 20px;
+$table-accent-bg: $gray-light;
+$card-border-color: $border-color;
+$card-cap-bg: $gray-light;
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index dbd3144b9b4..f2d296fb875 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -44,7 +44,7 @@
color: $gl-text-color-secondary;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 2b8163b8c68..af91497d0ea 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1158,7 +1158,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
a {
- color: $gl-link-color;
+ color: $blue-600;
}
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index a68b47b1d02..91f470ca709 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -225,7 +225,7 @@
outline: 0;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index e8158cd7f6b..1696d18584d 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -221,7 +221,7 @@
color: $gl-text-color;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index bce83bf0dd0..10764e0f3df 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -279,7 +279,7 @@
}
&.autodevops-link {
- color: $gl-link-color;
+ color: $blue-600;
}
}
@@ -321,7 +321,7 @@
}
.commit-sha {
- color: $gl-link-color;
+ color: $blue-600;
}
.commit-row-message {
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index e2c0a7a6225..bba9f38d3dd 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -360,7 +360,7 @@
}
.commit-sha {
- color: $gl-link-color;
+ color: $blue-600;
line-height: 1.3;
vertical-align: top;
font-weight: $gl-font-weight-normal;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 591e21243ed..47778110bae 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -511,13 +511,13 @@
padding: 0;
background-color: transparent;
border: 0;
- color: $gl-link-color;
+ color: $blue-600;
font-weight: $gl-font-weight-bold;
&:hover,
&:focus {
outline: none;
- color: $gl-link-hover-color;
+ color: $blue-800;
}
.caret-icon {
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index ddd1f8cc98a..892da152b5f 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -36,10 +36,7 @@
line-height: 35px;
padding-top: 7px;
padding-bottom: 7px;
-
- .float-right {
- height: 20px;
- }
+ display: flex;
}
.editor-ref {
@@ -60,20 +57,18 @@
.new-file-name {
display: inline-block;
- max-width: 450px;
+ max-width: 420px;
float: left;
@media(max-width: map-get($grid-breakpoints, lg)-1) {
- width: 280px;
- }
-
- @media(max-width: map-get($grid-breakpoints, md)-1) {
width: 180px;
}
}
.file-buttons {
- font-size: 0;
+ display: flex;
+ flex: 1;
+ justify-content: flex-end;
}
.select2 {
@@ -111,12 +106,10 @@
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
.file-editor {
.file-title {
- .float-right {
- height: auto;
- }
+ display: block;
}
.new-file-name {
@@ -144,6 +137,10 @@
}
}
}
+
+ .editor-ref {
+ max-width: 250px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 8a074017344..179c0964567 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -479,10 +479,10 @@
.deploy-info-text-link {
font-family: $monospace-font;
- fill: $gl-link-color;
+ fill: $blue-600;
&:hover {
- fill: $gl-link-hover-color;
+ fill: $blue-800;
}
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 49d8a5d959b..22fce893fd7 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -24,11 +24,11 @@
}
.graph-additions {
- color: $gl-text-green;
+ color: $green-600;
}
.graph-deletions {
- color: $gl-text-red;
+ color: $red-500;
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 1587aebfe1d..fa8a0f26b5d 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -419,6 +419,7 @@
.stats {
position: relative;
line-height: normal;
+ text-align: right;
flex-shrink: 0;
> span {
@@ -464,7 +465,7 @@
}
.last-updated {
- position: absolute;
+ position: relative;
right: 12px;
min-width: 250px;
text-align: right;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 8e78d9f65eb..d16a63d009a 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -141,7 +141,7 @@
color: inherit;
&:hover {
- color: $gl-link-hover-color;
+ color: $blue-800;
.avatar {
border-color: rgba($avatar-border, .2);
@@ -241,7 +241,7 @@
&:hover {
text-decoration: underline;
- color: $gl-link-hover-color;
+ color: $blue-800;
}
}
}
@@ -329,7 +329,7 @@
}
.btn-secondary-hover-link:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
.sidebar-collapsed-icon {
@@ -448,8 +448,8 @@
}
.todo-undone {
- color: $gl-link-color;
- fill: $gl-link-color;
+ color: $blue-600;
+ fill: $blue-600;
}
.author {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 212e5979273..0f95fb911e1 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -157,7 +157,7 @@ ul.related-merge-requests > li {
.issuable-email-modal-btn {
padding: 0;
- color: $gl-link-color;
+ color: $blue-600;
background-color: transparent;
border: 0;
outline: 0;
@@ -190,7 +190,7 @@ ul.related-merge-requests > li {
.create-mr-dropdown-wrap {
.ref::selection {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
.dropdown {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 2b40404971c..d32943fceec 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -114,7 +114,7 @@
}
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
&.remove-row {
color: $gl-danger;
@@ -253,7 +253,7 @@
text-align: right;
padding: 0;
position: relative;
- top: -3px;
+ margin: 0;
}
.label-badge {
@@ -274,6 +274,7 @@
.label-links {
list-style: none;
+ margin: 0;
padding: 0;
white-space: nowrap;
}
@@ -342,10 +343,10 @@
&.remove-row {
&:hover {
- color: $gl-text-red;
+ color: $red-500;
svg {
- fill: $gl-text-red;
+ fill: $red-500;
}
}
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 46437ce5841..1e92582d6d9 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -30,7 +30,7 @@
.milestone-progress {
a {
- color: $gl-link-color;
+ color: $blue-600;
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index dcf590e7331..4f861d43f55 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -69,7 +69,7 @@
.comment-toolbar,
.nav-links {
- border-color: $focus-border-color;
+ border-color: $blue-300;
}
}
@@ -306,7 +306,7 @@
&:hover,
&:focus {
- color: $gl-link-color;
+ color: $blue-600;
outline: 0;
}
@@ -424,7 +424,7 @@
.uploading-error-icon,
.uploading-error-message {
- color: $gl-text-red;
+ color: $red-500;
}
.uploading-error-message {
@@ -443,7 +443,7 @@
.attach-new-file,
.button-attach-file,
.retry-uploading-link {
- color: $gl-link-color;
+ color: $blue-600;
padding: 0;
background: none;
border: 0;
@@ -452,5 +452,5 @@
}
.markdown-selector {
- color: $gl-link-color;
+ color: $blue-600;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index c369d89d63c..8d28daac750 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -210,7 +210,7 @@ ul.notes {
}
a {
- color: $gl-link-color;
+ color: $blue-600;
}
p {
@@ -253,14 +253,14 @@ ul.notes {
overflow: hidden;
.system-note-commit-list-toggler {
- color: $gl-link-color;
+ color: $blue-600;
padding: 10px 0 0;
cursor: pointer;
position: relative;
z-index: 2;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: underline;
}
}
@@ -390,7 +390,7 @@ ul.notes {
color: inherit;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
&:focus,
@@ -451,7 +451,7 @@ ul.notes {
.discussion-headline-light {
a {
- color: $gl-link-color;
+ color: $blue-600;
}
}
@@ -560,12 +560,12 @@ ul.notes {
&:hover,
&.is-active {
.danger-highlight {
- color: $gl-text-red;
+ color: $red-500;
}
.link-highlight {
- color: $gl-link-color;
- fill: $gl-link-color;
+ color: $blue-600;
+ fill: $blue-600;
}
.award-control-icon-neutral {
@@ -597,13 +597,13 @@ ul.notes {
transition: color 0.1s linear;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
&:focus {
text-decoration: underline;
outline: none;
- color: $gl-link-color;
+ color: $blue-600;
}
.fa {
@@ -673,7 +673,7 @@ ul.notes {
}
a {
- color: $gl-link-color;
+ color: $blue-600;
}
}
@@ -759,16 +759,16 @@ ul.notes {
&:not(.is-disabled) {
&:hover,
&:focus {
- color: $gl-text-green;
+ color: $green-600;
}
}
&.is-active {
- color: $gl-text-green;
+ color: $green-600;
&:hover,
&:focus {
- color: $gl-text-green-hover;
+ color: $green-700;
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index b68c89c25d8..ad057ed3c83 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -175,7 +175,7 @@
}
.commit-sha {
- color: $gl-link-color;
+ color: $blue-600;
}
.badge {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 944421604fe..fffb440027c 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -388,7 +388,7 @@
line-height: $gl-btn-line-height;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
}
}
@@ -754,8 +754,19 @@
}
.repository-languages-bar {
- height: 6px;
- margin-bottom: 8px;
+ height: 8px;
+ margin-bottom: $gl-padding-8;
+ background-color: $white-light;
+ border-radius: $border-radius-default;
+
+ .progress-bar {
+ margin-right: 2px;
+ padding: 0 $gl-padding-4;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
}
pre.light-well {
@@ -823,10 +834,6 @@ pre.light-well {
.avatar-container {
align-self: flex-start;
-
- > a {
- width: 100%;
- }
}
.project-details {
@@ -954,7 +961,7 @@ pre.light-well {
margin-left: 5px;
&.is-done {
- color: $gl-text-green;
+ color: $green-600;
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 60b280fd12e..5b3a468cd1c 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -23,7 +23,7 @@ $search-avatar-size: 16px;
.search-text-input:hover,
.form-control:hover,
:not[readonly] {
- border-color: lighten($dropdown-input-focus-border, 20%);
+ border-color: lighten($blue-300, 20%);
box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
}
@@ -127,7 +127,7 @@ input[type='checkbox']:hover {
&.search-active {
form {
@extend .form-control:focus;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
box-shadow: none;
@include media-breakpoint-up(xl) {
@@ -259,6 +259,6 @@ input[type='checkbox']:hover {
&:hover,
&:focus {
- color: $gl-link-color;
+ color: $blue-600;
}
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 839ac5ba59b..fb03970f64f 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -301,3 +301,17 @@
margin-bottom: 0;
}
}
+
+.mirror-error-badge {
+ background-color: $error-bg;
+ border-radius: $border-radius-default;
+ color: $white-light;
+}
+
+.push-pull-table {
+ margin-top: 1em;
+
+ .mirror-action-buttons {
+ padding-right: 0;
+ }
+}
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 7a93c4dfa28..57d43beaf21 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -6,7 +6,7 @@
left: 0;
top: 0;
width: 100%;
- z-index: 2000;
+ z-index: 1039;
height: $performance-bar-height;
background: $black;
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 05ed3669a41..e5b38898a67 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -11,7 +11,6 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
- before_action :limit_unauthenticated_session_times
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
@@ -27,6 +26,7 @@ class ApplicationController < ActionController::Base
around_action :set_locale
after_action :set_page_title_header, if: :json_request?
+ after_action :limit_unauthenticated_session_times
protect_from_forgery with: :exception, prepend: true
diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb
index ed20302487c..461f26561f1 100644
--- a/app/controllers/notification_settings_controller.rb
+++ b/app/controllers/notification_settings_controller.rb
@@ -5,14 +5,14 @@ class NotificationSettingsController < ApplicationController
return render_404 unless can_read?(resource)
@notification_setting = current_user.notification_settings_for(resource)
- @saved = @notification_setting.update(notification_setting_params)
+ @saved = @notification_setting.update(notification_setting_params_for(resource))
render_response
end
def update
@notification_setting = current_user.notification_settings.find(params[:id])
- @saved = @notification_setting.update(notification_setting_params)
+ @saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source))
render_response
end
@@ -42,8 +42,8 @@ class NotificationSettingsController < ApplicationController
}
end
- def notification_setting_params
- allowed_fields = NotificationSetting::EMAIL_EVENTS.dup
+ def notification_setting_params_for(source)
+ allowed_fields = NotificationSetting.email_events(source).dup
allowed_fields << :level
params.require(:notification_setting).permit(allowed_fields)
end
diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb
index 07627ffb69f..a8f73ed5cb0 100644
--- a/app/controllers/projects/autocomplete_sources_controller.rb
+++ b/app/controllers/projects/autocomplete_sources_controller.rb
@@ -32,13 +32,8 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
end
def target
- case params[:type]&.downcase
- when 'issue'
- IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:type_id])
- when 'mergerequest'
- MergeRequestsFinder.new(current_user, project_id: @project.id).find_by(iid: params[:type_id])
- when 'commit'
- @project.commit(params[:type_id])
- end
+ QuickActions::TargetService
+ .new(project, current_user)
+ .execute(params[:type], params[:type_id])
end
end
diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb
new file mode 100644
index 00000000000..fad33f0eca2
--- /dev/null
+++ b/app/finders/license_template_finder.rb
@@ -0,0 +1,36 @@
+# LicenseTemplateFinder
+#
+# Used to find license templates, which may come from a variety of external
+# sources
+#
+# Arguments:
+# popular: boolean. When set to true, only "popular" licenses are shown. When
+# false, all licenses except popular ones are shown. When nil (the
+# default), *all* licenses will be shown.
+class LicenseTemplateFinder
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
+ def execute
+ Licensee::License.all(featured: popular_only?).map do |license|
+ LicenseTemplate.new(
+ id: license.key,
+ name: license.name,
+ nickname: license.nickname,
+ category: (license.featured? ? :Popular : :Other),
+ content: license.content,
+ url: license.url,
+ meta: license.meta
+ )
+ end
+ end
+
+ private
+
+ def popular_only?
+ params.fetch(:popular, nil)
+ end
+end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 2bdf2c2c120..1e05f07e676 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -254,6 +254,7 @@ module ApplicationSettingsHelper
:usage_ping_enabled,
:instance_statistics_visibility_private,
:user_default_external,
+ :user_show_add_ssh_key_message,
:user_oauth_applications,
:version_check_enabled,
:web_ide_clientside_preview_enabled
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index d48dae8f06d..494f785e305 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -1,28 +1,10 @@
module AvatarsHelper
def project_icon(project_id, options = {})
- project =
- if project_id.respond_to?(:avatar_url)
- project_id
- else
- Project.find_by_full_path(project_id)
- end
-
- if project.avatar_url
- image_tag project.avatar_url, options
- else # generated icon
- project_identicon(project, options)
- end
+ source_icon(Project, project_id, options)
end
- def project_identicon(project, options = {})
- bg_key = (project.id % 7) + 1
- options[:class] ||= ''
- options[:class] << ' identicon'
- options[:class] << " bg#{bg_key}"
-
- content_tag(:div, class: options[:class]) do
- project.name[0, 1].upcase
- end
+ def group_icon(group_id, options = {})
+ source_icon(Group, group_id, options)
end
# Takes both user and email and returns the avatar_icon by
@@ -123,4 +105,32 @@ module AvatarsHelper
mail_to(options[:user_email], avatar)
end
end
+
+ private
+
+ def source_icon(klass, source_id, options = {})
+ source =
+ if source_id.respond_to?(:avatar_url)
+ source_id
+ else
+ klass.find_by_full_path(source_id)
+ end
+
+ if source.avatar_url
+ image_tag source.avatar_url, options
+ else
+ source_identicon(source, options)
+ end
+ end
+
+ def source_identicon(source, options = {})
+ bg_key = (source.id % 7) + 1
+ options[:class] ||= ''
+ options[:class] << ' identicon'
+ options[:class] << " bg#{bg_key}"
+
+ content_tag(:div, class: options[:class].strip) do
+ source.name[0, 1].upcase
+ end
+ end
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 7eb45ddd117..b61cbd5418a 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -182,12 +182,14 @@ module BlobHelper
def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select)
- licenses = Licensee::License.all
+ grouped_licenses = LicenseTemplateFinder.new.execute.group_by(&:category)
+ categories = grouped_licenses.keys
- @licenses_for_select = {
- Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } },
- Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } }
- }
+ @licenses_for_select = categories.each_with_object({}) do |category, hash|
+ hash[category] = grouped_licenses[category].map do |license|
+ { name: license.name, id: license.id }
+ end
+ end
end
def ref_project
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 0171a880164..7adc882bc47 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -73,7 +73,11 @@ module ButtonHelper
end
def ssh_clone_button(project, append_link: true)
- dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile") if current_user.try(:require_ssh_key?)
+ if Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
+ current_user.try(:require_ssh_key?)
+ dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile")
+ end
+
append_url = project.ssh_url_to_repo if append_link
dropdown_item_with_description('SSH', dropdown_description, href: append_url)
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 3c5c8bbd71b..5b51d2f2425 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -33,11 +33,6 @@ module GroupsHelper
.count
end
- def group_icon(group, options = {})
- img_path = group_icon_url(group, options)
- image_tag img_path, options
- end
-
def group_icon_url(group, options = {})
if group.is_a?(String)
group = Group.find_by_full_path(group)
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 41084ec686f..a8a10c98d69 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -62,6 +62,8 @@ module IconsHelper
names = "key"
when "two-factor"
names = "key"
+ when "google_oauth2"
+ names = "google"
end
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb
new file mode 100644
index 00000000000..93ed22513ac
--- /dev/null
+++ b/app/helpers/mirror_helper.rb
@@ -0,0 +1,5 @@
+module MirrorHelper
+ def mirrors_form_data_attributes
+ { project_mirror_endpoint: project_mirror_path(@project) }
+ end
+end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 3e42063224e..a185f2916d4 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -83,21 +83,11 @@ module NotificationsHelper
end
def notification_event_name(event)
- # All values from NotificationSetting::EMAIL_EVENTS
+ # All values from NotificationSetting.email_events
case event
when :success_pipeline
s_('NotificationEvent|Successful pipeline')
else
- N_('NotificationEvent|New note')
- N_('NotificationEvent|New issue')
- N_('NotificationEvent|Reopen issue')
- N_('NotificationEvent|Close issue')
- N_('NotificationEvent|Reassign issue')
- N_('NotificationEvent|New merge request')
- N_('NotificationEvent|Close merge request')
- N_('NotificationEvent|Reassign merge request')
- N_('NotificationEvent|Merge merge request')
- N_('NotificationEvent|Failed pipeline')
s_(event.to_s.humanize)
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index aaf9dff43ee..6b4079b4113 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -192,7 +192,10 @@ module ProjectsHelper
end
def show_no_ssh_key_message?
- cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+ Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
+ cookies[:hide_no_ssh_message].blank? &&
+ !current_user.hide_no_ssh_key &&
+ current_user.require_ssh_key?
end
def show_no_password_message?
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
index fe5f68ba3d5..e032f568913 100644
--- a/app/mailers/abuse_report_mailer.rb
+++ b/app/mailers/abuse_report_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AbuseReportMailer < BaseMailer
def notify(abuse_report_id)
return unless deliverable?
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 654468bc7fe..5fd209c4761 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BaseMailer < ActionMailer::Base
around_action :render_with_default_locale
diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb
index 962570a0efd..7aa75ee30e6 100644
--- a/app/mailers/devise_mailer.rb
+++ b/app/mailers/devise_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DeviseMailer < Devise::Mailer
default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>"
default reply_to: Gitlab.config.gitlab.email_reply_to
@@ -9,8 +11,9 @@ class DeviseMailer < Devise::Mailer
protected
def subject_for(key)
- subject = super
- subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
- subject
+ subject = [super]
+ subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?
+
+ subject.join(' | ')
end
end
diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb
index 76db31a4c45..45fc5a6c383 100644
--- a/app/mailers/email_rejection_mailer.rb
+++ b/app/mailers/email_rejection_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailRejectionMailer < BaseMailer
def rejection(reason, original_raw, can_retry = false)
@reason = reason
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 392cc0bee03..c8b1ab5033a 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Issues
def new_issue_email(recipient_id, issue_id, reason = nil)
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 75cf56a51f2..91dfdf58982 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Members
extend ActiveSupport::Concern
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 70509e9066d..70f65d4e58d 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module MergeRequests
def new_merge_request_email(recipient_id, merge_request_id, reason = nil)
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index d9a6fe2a41e..d3284e90568 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Notes
def note_commit_email(recipient_id, note_id)
diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb
index 0027dfdc36b..ce449237ef6 100644
--- a/app/mailers/emails/pages_domains.rb
+++ b/app/mailers/emails/pages_domains.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module PagesDomains
def pages_domain_enabled_email(domain, recipient)
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index f9f45ab987b..31e183640ad 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Pipelines
def pipeline_success_email(pipeline, recipients)
@@ -39,10 +41,10 @@ module Emails
end
def pipeline_subject(status)
- commit = @pipeline.short_sha
- commit << " in #{@merge_request.to_reference}" if @merge_request
+ commit = [@pipeline.short_sha]
+ commit << "in #{@merge_request.to_reference}" if @merge_request
- subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit)
+ subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit.join(' '))
end
end
end
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 4f5edeb9bda..40d7b9ccd7a 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Profile
def new_user_email(user_id, token = nil)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 761d873c01c..d7e6c2ba7b2 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Projects
def project_was_moved_email(project_id, user_id, old_path_with_namespace)
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0e1e39501f5..f4eeb85270e 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes
include GitlabRoutingHelper
@@ -92,12 +94,14 @@ class Notify < BaseMailer
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "Lorem ipsum | Dolor sit amet"
def subject(*extra)
- subject = ""
- subject << "#{@project.name} | " if @project
- subject << "#{@group.name} | " if @group
- subject << extra.join(' | ') if extra.present?
- subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
- subject
+ subject = []
+
+ subject << @project.name if @project
+ subject << @group.name if @group
+ subject.concat(extra) if extra.present?
+ subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?
+
+ subject.join(' | ')
end
# Return a string suitable for inclusion in the 'Message-Id' mail header.
diff --git a/app/mailers/previews/devise_mailer_preview.rb b/app/mailers/previews/devise_mailer_preview.rb
index d6588efc486..3b9ef0d3ac0 100644
--- a/app/mailers/previews/devise_mailer_preview.rb
+++ b/app/mailers/previews/devise_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DeviseMailerPreview < ActionMailer::Preview
def confirmation_instructions_for_signup
DeviseMailer.confirmation_instructions(unsaved_user, 'faketoken', {})
diff --git a/app/mailers/previews/email_rejection_mailer_preview.rb b/app/mailers/previews/email_rejection_mailer_preview.rb
index 639e8471232..402066151ef 100644
--- a/app/mailers/previews/email_rejection_mailer_preview.rb
+++ b/app/mailers/previews/email_rejection_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailRejectionMailerPreview < ActionMailer::Preview
def rejection
EmailRejectionMailer.rejection("some rejection reason", "From: someone@example.com\nraw email here").message
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 3615cde8026..df470930e9e 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class NotifyPreview < ActionMailer::Preview
def note_merge_request_email_for_individual_note
note_email(:note_merge_request_email) do
diff --git a/app/mailers/previews/repository_check_mailer_preview.rb b/app/mailers/previews/repository_check_mailer_preview.rb
index 19d4eab1805..834d7594719 100644
--- a/app/mailers/previews/repository_check_mailer_preview.rb
+++ b/app/mailers/previews/repository_check_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryCheckMailerPreview < ActionMailer::Preview
def notify
RepositoryCheckMailer.notify(3).message
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 22a9f5da646..4bcf371cfc0 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryCheckMailer < BaseMailer
def notify(failed_count)
@message =
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index bbe7811841a..c77faa4b71d 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -298,7 +298,8 @@ class ApplicationSetting < ActiveRecord::Base
unique_ips_limit_time_window: 3600,
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false,
- user_default_external: false
+ user_default_external: false,
+ user_show_add_ssh_key_message: true
}
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 9292929be98..3c69677baf0 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -226,7 +226,7 @@ module Ci
end
def cancelable?
- active?
+ active? || created?
end
def retryable?
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index d7c5f29be96..17b7ee4f07e 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -33,7 +33,7 @@ module Ci
where(file_type: types)
end
- delegate :exists?, :open, to: :file
+ delegate :filename, :exists?, :open, to: :file
enum file_type: {
archive: 1,
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 8b9f4490ffa..27fbdc3e386 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -448,6 +448,10 @@ class Commit
true
end
+ def to_ability_name
+ model_name.singular
+ end
+
def touch
# no-op but needs to be defined since #persisted? is defined
end
diff --git a/app/models/license_template.rb b/app/models/license_template.rb
new file mode 100644
index 00000000000..0ad75b27827
--- /dev/null
+++ b/app/models/license_template.rb
@@ -0,0 +1,53 @@
+class LicenseTemplate
+ PROJECT_TEMPLATE_REGEX =
+ %r{[\<\{\[]
+ (project|description|
+ one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
+ [\>\}\]]}xi.freeze
+ YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
+ FULLNAME_TEMPLATE_REGEX =
+ %r{[\<\{\[]
+ (fullname|name\sof\s(author|copyright\sowner))
+ [\>\}\]]}xi.freeze
+
+ attr_reader :id, :name, :category, :nickname, :url, :meta
+
+ alias_method :key, :id
+
+ def initialize(id:, name:, category:, content:, nickname: nil, url: nil, meta: {})
+ @id = id
+ @name = name
+ @category = category
+ @content = content
+ @nickname = nickname
+ @url = url
+ @meta = meta
+ end
+
+ def popular?
+ category == :Popular
+ end
+ alias_method :featured?, :popular?
+
+ # Returns the text of the license
+ def content
+ if @content.respond_to?(:call)
+ @content = @content.call
+ else
+ @content
+ end
+ end
+
+ # Populate placeholders in the LicenseTemplate content
+ def resolve!(project_name: nil, fullname: nil, year: Time.now.year.to_s)
+ # Ensure the string isn't shared with any other instance of LicenseTemplate
+ new_content = content.dup
+ new_content.gsub!(YEAR_TEMPLATE_REGEX, year) if year.present?
+ new_content.gsub!(PROJECT_TEMPLATE_REGEX, project_name) if project_name.present?
+ new_content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname.present?
+
+ @content = new_content
+
+ self
+ end
+end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b974309aeb6..0deb44d7916 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -10,6 +10,7 @@ class Namespace < ActiveRecord::Base
include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
include IgnorableColumn
+ include FeatureGate
ignore_column :deleted_at
@@ -124,7 +125,6 @@ class Namespace < ActiveRecord::Base
def to_param
full_path
end
- alias_method :flipper_id, :to_param
def human_name
owner_name
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 1df3a51a7fc..1600acfc575 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -45,6 +45,15 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline
].freeze
+ # Update unfound_translations.rb when events are changed
+ def self.email_events(source = nil)
+ EMAIL_EVENTS
+ end
+
+ def email_events
+ self.class.email_events(source)
+ end
+
EXCLUDED_PARTICIPATING_EVENTS = [
:success_pipeline
].freeze
diff --git a/app/models/programming_language.rb b/app/models/programming_language.rb
index 400d6c407a7..0e667dac21e 100644
--- a/app/models/programming_language.rb
+++ b/app/models/programming_language.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProgrammingLanguage < ActiveRecord::Base
validates :name, presence: true
validates :color, allow_blank: false, color: true
diff --git a/app/models/project.rb b/app/models/project.rb
index 36089995ed3..7735f23cb9e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -470,6 +470,24 @@ class Project < ActiveRecord::Base
}x
end
+ def reference_postfix
+ '>'
+ end
+
+ def reference_postfix_escaped
+ '&gt;'
+ end
+
+ # Pattern used to extract `namespace/project>` project references from text.
+ # '>' or its escaped form ('&gt;') are checked for because '>' is sometimes escaped
+ # when the reference comes from an external source.
+ def markdown_reference_pattern
+ %r{
+ #{reference_pattern}
+ (#{reference_postfix}|#{reference_postfix_escaped})
+ }x
+ end
+
def trending
joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id')
.reorder('trending_projects.id ASC')
@@ -908,6 +926,10 @@ class Project < ActiveRecord::Base
end
end
+ def to_reference_with_postfix
+ "#{to_reference(full: true)}#{self.class.reference_postfix}"
+ end
+
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
if full || cross_namespace_reference?(from)
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 4f289e6e215..35c19049c04 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'asana'
class AsanaService < Service
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index 4234b8044e5..60575e45a90 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AssemblaService < Service
prop_accessor :token, :subdomain
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index edc5c00d9c4..d502423726c 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BambooService < CiService
include ReactiveService
diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb
index e4e3a80976b..1a2bb6a171b 100644
--- a/app/models/project_services/bugzilla_service.rb
+++ b/app/models/project_services/bugzilla_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BugzillaService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 35884c4560c..43edfde851c 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "addressable/uri"
class BuildkiteService < CiService
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 0c526b53d72..f2295a95b60 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class is to be removed with 9.1
# We should also by then remove BuildsEmailService from database
class BuildsEmailService < Service
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index cb4af73807b..1d7877a1fb5 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class CampfireService < Service
prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated?
@@ -82,7 +84,7 @@ class CampfireService < Service
before = push[:before]
after = push[:after]
- message = ""
+ message = []
message << "[#{project.full_name}] "
message << "#{push[:user_name]} "
@@ -95,6 +97,6 @@ class CampfireService < Service
message << "#{project.web_url}/compare/#{before}...#{after}"
end
- message
+ message.join
end
end
diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb
index f710fa85b5d..8c68ddc40f2 100644
--- a/app/models/project_services/chat_message/base_message.rb
+++ b/app/models/project_services/chat_message/base_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'slack-notifier'
module ChatMessage
diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb
index 3273f41dbd2..0cdcfcf0237 100644
--- a/app/models/project_services/chat_message/issue_message.rb
+++ b/app/models/project_services/chat_message/issue_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class IssueMessage < BaseMessage
attr_reader :title
diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb
index f412b6833d9..58631e09538 100644
--- a/app/models/project_services/chat_message/merge_message.rb
+++ b/app/models/project_services/chat_message/merge_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class MergeMessage < BaseMessage
attr_reader :merge_request_iid
diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb
index 7f9486132e6..741474fb27b 100644
--- a/app/models/project_services/chat_message/note_message.rb
+++ b/app/models/project_services/chat_message/note_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class NoteMessage < BaseMessage
attr_reader :note
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 96fd23aede3..62aec4351db 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class PipelineMessage < BaseMessage
attr_reader :ref_type
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index 8d599c5f116..82be33a12a1 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class PushMessage < BaseMessage
attr_reader :after
diff --git a/app/models/project_services/chat_message/wiki_page_message.rb b/app/models/project_services/chat_message/wiki_page_message.rb
index d84b80f2de2..b605d289278 100644
--- a/app/models/project_services/chat_message/wiki_page_message.rb
+++ b/app/models/project_services/chat_message/wiki_page_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class WikiPageMessage < BaseMessage
attr_reader :title
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index a60b4c7fd0d..c10ee07ccf4 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for Chat notifications services
# This class is not meant to be used directly, but only to inherit from.
class ChatNotificationService < Service
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 82979c8bd34..f0ef2d925ab 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for CI services
# List methods you need to implement to get your CI service
# working with GitLab Merge Requests
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index 456c7f5cee2..b8f8072869c 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class CustomIssueTrackerService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb
index 5b8320158fc..6dae4f3a4a6 100644
--- a/app/models/project_services/deployment_service.rb
+++ b/app/models/project_services/deployment_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for deployment services
#
# These services integrate with a deployment solution like Kubernetes/OpenShift,
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index ab4e46da89f..158ae0bf255 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DroneCiService < CiService
include ReactiveService
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index b604d860a87..fb73d430fb1 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailsOnPushService < Service
boolean_accessor :send_from_committer_email
boolean_accessor :disable_diffs
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index a4b1ef09e93..d2835c6ac82 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ExternalWikiService < Service
prop_accessor :external_wiki_url
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index da01ac1b7cf..2545df06f6b 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "flowdock-git-hook"
# Flow dock depends on Grit to compute the number of commits between two given
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 8a6b0ed1a5f..67a92c441b1 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "gemnasium/gitlab_service"
class GemnasiumService < Service
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 16e32a4139e..fa9abf58e62 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GitlabIssueTrackerService < IssueTrackerService
include Gitlab::Routing
diff --git a/app/models/project_services/hangouts_chat_service.rb b/app/models/project_services/hangouts_chat_service.rb
index a8512c5f57c..272cd0f4e47 100644
--- a/app/models/project_services/hangouts_chat_service.rb
+++ b/app/models/project_services/hangouts_chat_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'hangouts_chat'
class HangoutsChatService < ChatNotificationService
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index dce878e485f..66012f0da99 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class HipchatService < Service
include ActionView::Helpers::SanitizeHelper
@@ -108,7 +110,7 @@ class HipchatService < Service
before = push[:before]
after = push[:after]
- message = ""
+ message = []
message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before)
@@ -132,7 +134,7 @@ class HipchatService < Service
end
end
- message
+ message.join
end
def markdown(text, options = {})
@@ -165,11 +167,11 @@ class HipchatService < Service
description = obj_attr[:description]
issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
- message = "#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"
+ message = ["#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>"
- message
+ message.join
end
def create_merge_request_message(data)
@@ -184,12 +186,11 @@ class HipchatService < Service
merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
- message = "#{user_name} #{state} #{merge_request_link} in " \
- "#{project_link}: <b>#{title}</b>"
+ message = ["#{user_name} #{state} #{merge_request_link} in " \
+ "#{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>"
-
- message
+ message.join
end
def format_title(title)
@@ -235,12 +236,11 @@ class HipchatService < Service
end
subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
- message = "#{user_name} commented on #{subject_html} in #{project_link}: "
+ message = ["#{user_name} commented on #{subject_html} in #{project_link}: "]
message << title
message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
-
- message
+ message.join
end
def create_pipeline_message(data)
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 27bdf708c80..a783a314071 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'uri'
class IrkerService < Service
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index df6dcd90985..c7520d766a8 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class IssueTrackerService < Service
validate :one_issue_tracker, if: :activated?, on: :manual_change
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 82d438d5378..cc98b3f5a41 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class JiraService < IssueTrackerService
include Gitlab::Routing
include ApplicationHelper
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 722642f6da7..bda1f67b8ff 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
##
# NOTE:
# We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic.
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index 0362ed172c7..b8bc83b870e 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MattermostService < ChatNotificationService
def title
'Mattermost notifications'
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 227d430083d..ca324f68d2d 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MattermostSlashCommandsService < SlashCommandsService
include TriggersHelper
diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb
index 99500caec0e..5b0e5fed092 100644
--- a/app/models/project_services/microsoft_teams_service.rb
+++ b/app/models/project_services/microsoft_teams_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MicrosoftTeamsService < ChatNotificationService
def title
'Microsoft Teams Notification'
diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb
index b89dc07a73e..6883976f0c8 100644
--- a/app/models/project_services/mock_ci_service.rb
+++ b/app/models/project_services/mock_ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
class MockCiService < CiService
ALLOWED_STATES = %w[failed canceled running pending success success_with_warnings skipped not_found].freeze
diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb
index 59a3811ce5d..7ab1687f8ba 100644
--- a/app/models/project_services/mock_deployment_service.rb
+++ b/app/models/project_services/mock_deployment_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MockDeploymentService < DeploymentService
def title
'Mock deployment'
diff --git a/app/models/project_services/mock_monitoring_service.rb b/app/models/project_services/mock_monitoring_service.rb
index ed0318c6b27..bcf8f1df5da 100644
--- a/app/models/project_services/mock_monitoring_service.rb
+++ b/app/models/project_services/mock_monitoring_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MockMonitoringService < MonitoringService
def title
'Mock monitoring'
diff --git a/app/models/project_services/monitoring_service.rb b/app/models/project_services/monitoring_service.rb
index 9af68b4e821..1b530a8247b 100644
--- a/app/models/project_services/monitoring_service.rb
+++ b/app/models/project_services/monitoring_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for monitoring services
#
# These services integrate with a deployment solution like Prometheus
diff --git a/app/models/project_services/packagist_service.rb b/app/models/project_services/packagist_service.rb
index ba62a5b7ac0..003884bb7ac 100644
--- a/app/models/project_services/packagist_service.rb
+++ b/app/models/project_services/packagist_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PackagistService < Service
prop_accessor :username, :token, :server
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index 4cf149ac044..6f39a5e6e83 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PipelinesEmailService < Service
prop_accessor :recipients
boolean_accessor :notify_only_broken_pipelines
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 3476e7d2283..617e502b639 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PivotaltrackerService < Service
API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index df4254e0523..509e5b6089b 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PrometheusService < MonitoringService
include PrometheusAdapter
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 8777a44b72f..4e48c348b45 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PushoverService < Service
BASE_URI = 'https://api.pushover.net/1'.freeze
@@ -79,7 +81,7 @@ class PushoverService < Service
end
if data[:total_commits_count] > 0
- message << "\nTotal commits count: #{data[:total_commits_count]}"
+ message = [message, "Total commits count: #{data[:total_commits_count]}"].join("\n")
end
pushover_data = {
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index 3721093a6d1..a80be4b06da 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RedmineService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 71da0af75f6..482808255f9 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SlackService < ChatNotificationService
def title
'Slack notifications'
diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb
index 1c3892a3f75..6c82e088231 100644
--- a/app/models/project_services/slack_slash_commands_service.rb
+++ b/app/models/project_services/slack_slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SlackSlashCommandsService < SlashCommandsService
include TriggersHelper
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index 37ea45109ae..e3ab60adefd 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for Chat services
# This class is not meant to be used directly, but only to inherrit from.
class SlashCommandsService < Service
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 802678147cf..eeeff5e802a 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TeamcityService < CiService
include ReactiveService
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
index e8d35ac326f..b0d5c64e931 100644
--- a/app/models/protected_branch/merge_access_level.rb
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
index 7a2e9e5ec5d..b2a88229853 100644
--- a/app/models/protected_branch/push_access_level.rb
+++ b/app/models/protected_branch/push_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
end
diff --git a/app/models/protected_tag/create_access_level.rb b/app/models/protected_tag/create_access_level.rb
index 6b6ab3d8279..b06e55fb5dd 100644
--- a/app/models/protected_tag/create_access_level.rb
+++ b/app/models/protected_tag/create_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedTag::CreateAccessLevel < ActiveRecord::Base
include ProtectedTagAccess
diff --git a/app/models/repository_language.rb b/app/models/repository_language.rb
index f467d4eafa3..b18142a2ac4 100644
--- a/app/models/repository_language.rb
+++ b/app/models/repository_language.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryLanguage < ActiveRecord::Base
belongs_to :project
belongs_to :programming_language
diff --git a/app/models/site_statistic.rb b/app/models/site_statistic.rb
index 9c9c3172fe6..daac1c57db9 100644
--- a/app/models/site_statistic.rb
+++ b/app/models/site_statistic.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SiteStatistic < ActiveRecord::Base
# prevents the creation of multiple rows
default_value_for :id, 1
diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb
index 26b4b78ac64..90710f73fd3 100644
--- a/app/models/storage/hashed_project.rb
+++ b/app/models/storage/hashed_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
class HashedProject
attr_accessor :project
diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb
index 27cb388c702..9f6f19acb41 100644
--- a/app/models/storage/legacy_project.rb
+++ b/app/models/storage/legacy_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
class LegacyProject
attr_accessor :project
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index c5c77bc8333..376ef673ca8 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -15,7 +15,7 @@ class SystemNoteMetadata < ActiveRecord::Base
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved
opened closed merged duplicate locked unlocked
- outdated
+ outdated tag
].freeze
validates :note, presence: true
diff --git a/app/models/user.rb b/app/models/user.rb
index 37f2e8b680e..fb19de4b980 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -101,6 +101,10 @@ class User < ActiveRecord::Base
has_many :groups, through: :group_members
has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group
+ has_many :owned_or_maintainers_groups,
+ -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
+ through: :group_members,
+ source: :group
alias_attribute :masters_groups, :maintainers_groups
# Projects
@@ -982,15 +986,7 @@ class User < ActiveRecord::Base
end
def manageable_groups
- union_sql = Gitlab::SQL::Union.new([owned_groups.select(:id), maintainers_groups.select(:id)]).to_sql
-
- # Update this line to not use raw SQL when migrated to Rails 5.2.
- # Either ActiveRecord or Arel constructions are fine.
- # This was replaced with the raw SQL construction because of bugs in the arel gem.
- # Bugs were fixed in arel 9.0.0 (Rails 5.2).
- owned_and_maintainer_groups = Group.where("namespaces.id IN (#{union_sql})") # rubocop:disable GitlabSecurity/SqlInjection
-
- Gitlab::GroupHierarchy.new(owned_and_maintainer_groups).base_and_descendants
+ Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants
end
def namespaces
@@ -1244,11 +1240,6 @@ class User < ActiveRecord::Base
!terms_accepted?
end
- def owned_or_maintainers_groups
- union = Gitlab::SQL::Union.new([owned_groups, maintainers_groups])
- Group.from("(#{union.to_sql}) namespaces")
- end
-
# @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
diff --git a/app/policies/commit_policy.rb b/app/policies/commit_policy.rb
new file mode 100644
index 00000000000..67e9bc12804
--- /dev/null
+++ b/app/policies/commit_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class CommitPolicy < BasePolicy
+ delegate { @subject.project }
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index f52a3bad77d..00c58f15013 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -251,6 +251,7 @@ class ProjectPolicy < BasePolicy
enable :update_pages
enable :read_cluster
enable :create_cluster
+ enable :create_environment_terminal
end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 02f6c5bdf81..880218e2727 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Ci
class BuildRunnerPresenter < SimpleDelegator
def artifacts
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index b18e9706db6..07a13c33b89 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -23,9 +23,8 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment)
end
- expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment|
- can?(request.current_user, :admin_environment, environment.project) &&
- terminal_project_environment_path(environment.project, environment)
+ expose :terminal_path, if: ->(*) { environment.has_terminals? && can_access_terminal? } do |environment|
+ terminal_project_environment_path(environment.project, environment)
end
expose :folder_path do |environment|
@@ -40,7 +39,13 @@ class EnvironmentEntity < Grape::Entity
private
+ alias_method :environment, :object
+
def current_user
request.current_user
end
+
+ def can_access_terminal?
+ can?(request.current_user, :create_environment_terminal, environment)
+ end
end
diff --git a/app/serializers/project_mirror_serializer.rb b/app/serializers/project_mirror_serializer.rb
new file mode 100644
index 00000000000..6a9462aa7cb
--- /dev/null
+++ b/app/serializers/project_mirror_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ProjectMirrorSerializer < BaseSerializer
+ entity ProjectMirrorEntity
+end
diff --git a/app/serializers/test_case_entity.rb b/app/serializers/test_case_entity.rb
index 5c1cbf37182..ec60055ba5b 100644
--- a/app/serializers/test_case_entity.rb
+++ b/app/serializers/test_case_entity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TestCaseEntity < Grape::Entity
expose :status
expose :name
diff --git a/app/serializers/test_reports_comparer_entity.rb b/app/serializers/test_reports_comparer_entity.rb
index b95d820d093..d7a3dd34fdc 100644
--- a/app/serializers/test_reports_comparer_entity.rb
+++ b/app/serializers/test_reports_comparer_entity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TestReportsComparerEntity < Grape::Entity
expose :total_status, as: :status
diff --git a/app/serializers/test_reports_comparer_serializer.rb b/app/serializers/test_reports_comparer_serializer.rb
index a739858efb2..7fb8d28b09a 100644
--- a/app/serializers/test_reports_comparer_serializer.rb
+++ b/app/serializers/test_reports_comparer_serializer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TestReportsComparerSerializer < BaseSerializer
entity TestReportsComparerEntity
end
diff --git a/app/serializers/test_suite_comparer_entity.rb b/app/serializers/test_suite_comparer_entity.rb
index a3965ba3930..9fa3a897ebe 100644
--- a/app/serializers/test_suite_comparer_entity.rb
+++ b/app/serializers/test_suite_comparer_entity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TestSuiteComparerEntity < Grape::Entity
expose :name
expose :total_status, as: :status
diff --git a/app/services/ci/enqueue_build_service.rb b/app/services/ci/enqueue_build_service.rb
new file mode 100644
index 00000000000..8140651d980
--- /dev/null
+++ b/app/services/ci/enqueue_build_service.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Ci
+ class EnqueueBuildService < BaseService
+ def execute(build)
+ build.enqueue
+ end
+ end
+end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index cda9bbff3b4..cafee76a33c 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -37,7 +37,7 @@ module Ci
def process_build(build, current_status)
if valid_statuses_for_when(build.when).include?(current_status)
- build.action? ? build.actionize : build.enqueue
+ build.action? ? build.actionize : enqueue_build(build)
true
else
build.skip
@@ -93,5 +93,9 @@ module Ci
.where.not(id: latest_statuses.map(&:first))
.update_all(retried: true) if latest_statuses.any?
end
+
+ def enqueue_build(build)
+ Ci::EnqueueBuildService.new(project, @user).execute(build)
+ end
end
end
diff --git a/app/services/commits/tag_service.rb b/app/services/commits/tag_service.rb
new file mode 100644
index 00000000000..7961ba4d3c4
--- /dev/null
+++ b/app/services/commits/tag_service.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Commits
+ class TagService < BaseService
+ def execute(commit)
+ unless params[:tag_name]
+ return error('Missing parameter tag_name')
+ end
+
+ tag_name = params[:tag_name]
+ message = params[:tag_message]
+ release_description = nil
+
+ result = Tags::CreateService
+ .new(commit.project, current_user)
+ .execute(tag_name, commit.sha, message, release_description)
+
+ if result[:status] == :success
+ tag = result[:tag]
+ SystemNoteService.tag_commit(commit, commit.project, current_user, tag.name)
+ end
+
+ result
+ end
+ end
+end
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index 7280449bb1c..4c14d834949 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -4,7 +4,8 @@ module Notes
class QuickActionsService < BaseService
UPDATE_SERVICES = {
'Issue' => Issues::UpdateService,
- 'MergeRequest' => MergeRequests::UpdateService
+ 'MergeRequest' => MergeRequests::UpdateService,
+ 'Commit' => Commits::TagService
}.freeze
def self.noteable_update_service(note)
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 4389fd89538..5c0e8a35cb0 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -130,7 +130,7 @@ module NotificationRecipientService
end
def add_project_watchers
- add_recipients(project_watchers, :watch, nil)
+ add_recipients(project_watchers, :watch, nil) if project
end
def add_group_watchers
@@ -220,6 +220,8 @@ module NotificationRecipientService
end
class Default < Base
+ MENTION_TYPE_ACTIONS = [:new_issue, :new_merge_request].freeze
+
attr_reader :target
attr_reader :current_user
attr_reader :action
@@ -252,7 +254,7 @@ module NotificationRecipientService
add_subscribed_users
- if [:new_issue, :new_merge_request].include?(custom_action)
+ if self.class.mention_type_actions.include?(custom_action)
# These will all be participants as well, but adding with the :mention
# type ensures that users with the mention notification level will
# receive them, too.
@@ -279,10 +281,14 @@ module NotificationRecipientService
end
# Build event key to search on custom notification level
- # Check NotificationSetting::EMAIL_EVENTS
+ # Check NotificationSetting.email_events
def custom_action
@custom_action ||= "#{action}_#{target.class.model_name.name.underscore}".to_sym
end
+
+ def self.mention_type_actions
+ MENTION_TYPE_ACTIONS.dup
+ end
end
class NewNote < Base
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index a15ee4911ef..11b996ed4b6 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -16,7 +16,7 @@ class PreviewMarkdownService < BaseService
private
def explain_quick_actions(text)
- return text, [] unless %w(Issue MergeRequest).include?(commands_target_type)
+ return text, [] unless %w(Issue MergeRequest Commit).include?(commands_target_type)
quick_actions_service = QuickActions::InterpretService.new(project, current_user)
quick_actions_service.explain(text, find_commands_target)
@@ -29,13 +29,9 @@ class PreviewMarkdownService < BaseService
end
def find_commands_target
- if commands_target_id.present?
- finder = commands_target_type == 'Issue' ? IssuesFinder : MergeRequestsFinder
- finder.new(current_user, project_id: project.id).find(commands_target_id)
- else
- collection = commands_target_type == 'Issue' ? project.issues : project.merge_requests
- collection.build
- end
+ QuickActions::TargetService
+ .new(project, current_user)
+ .execute(commands_target_type, commands_target_id)
end
def commands_target_type
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 10eb2cea4a2..5286b92ab6b 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -47,15 +47,7 @@ module Projects
end
def commands(noteable, type)
- noteable ||=
- case type
- when 'Issue'
- @project.issues.build
- when 'MergeRequest'
- @project.merge_requests.build
- end
-
- return [] unless noteable&.is_a?(Issuable)
+ return [] unless noteable
QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
end
diff --git a/app/services/projects/detect_repository_languages_service.rb b/app/services/projects/detect_repository_languages_service.rb
index 4b4108de231..3488b9ce47e 100644
--- a/app/services/projects/detect_repository_languages_service.rb
+++ b/app/services/projects/detect_repository_languages_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Projects
class DetectRepositoryLanguagesService < BaseService
attr_reader :detected_repository_languages, :programming_languages
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index cdc8514c47c..8838ed06324 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -60,7 +60,8 @@ module QuickActions
"Closes this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.open? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
@@ -75,7 +76,8 @@ module QuickActions
"Reopens this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.closed? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
@@ -149,7 +151,8 @@ module QuickActions
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -188,7 +191,8 @@ module QuickActions
"Removes #{issuable.milestone.to_reference(format: :name)} milestone."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.milestone_id? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -231,7 +235,8 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -257,7 +262,8 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -295,7 +301,8 @@ module QuickActions
desc 'Add a todo'
explanation 'Adds a todo.'
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
!TodoService.new.todo_exist?(issuable, current_user)
end
command :todo do
@@ -317,7 +324,8 @@ module QuickActions
"Subscribes to this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
!issuable.subscribed?(current_user, project)
end
command :subscribe do
@@ -329,7 +337,8 @@ module QuickActions
"Unsubscribes from this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.subscribed?(current_user, project)
end
command :unsubscribe do
@@ -385,7 +394,8 @@ module QuickActions
end
params ':emoji:'
condition do
- issuable.persisted?
+ issuable.is_a?(Issuable) &&
+ issuable.persisted?
end
parse_params do |emoji_param|
match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern)
@@ -574,6 +584,23 @@ module QuickActions
@updates[:confidential] = true
end
+ desc 'Tag this commit.'
+ explanation do |tag_name, message|
+ with_message = %{ with "#{message}"} if message.present?
+ "Tags this commit to #{tag_name}#{with_message}."
+ end
+ params 'v1.2.3 <message>'
+ parse_params do |tag_name_and_message|
+ tag_name_and_message.split(' ', 2)
+ end
+ condition do
+ issuable.is_a?(Commit) && current_user.can?(:push_code, project)
+ end
+ command :tag do |tag_name, message|
+ @updates[:tag_name] = tag_name
+ @updates[:tag_message] = message
+ end
+
def extract_users(params)
return [] if params.nil?
diff --git a/app/services/quick_actions/target_service.rb b/app/services/quick_actions/target_service.rb
new file mode 100644
index 00000000000..d8ba52c6e50
--- /dev/null
+++ b/app/services/quick_actions/target_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module QuickActions
+ class TargetService < BaseService
+ def execute(type, type_id)
+ case type&.downcase
+ when 'issue'
+ issue(type_id)
+ when 'mergerequest'
+ merge_request(type_id)
+ when 'commit'
+ commit(type_id)
+ end
+ end
+
+ private
+
+ def issue(type_id)
+ IssuesFinder.new(current_user, project_id: project.id).find_by(iid: type_id) || project.issues.build
+ end
+
+ def merge_request(type_id)
+ MergeRequestsFinder.new(current_user, project_id: project.id).find_by(iid: type_id) || project.merge_requests.build
+ end
+
+ def commit(type_id)
+ project.commit(type_id)
+ end
+ end
+end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 77494295f14..dda89830179 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -32,6 +32,21 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
end
+ # Called when a commit was tagged
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the tag
+ # tag_name - The created tag name
+ #
+ # Returns the created Note object
+ def tag_commit(noteable, project, author, tag_name)
+ link = url_helpers.project_tag_url(project, id: tag_name)
+ body = "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'tag'))
+ end
+
# Called when the assignee of a Noteable is changed or removed
#
# noteable - Noteable object
diff --git a/app/services/tags/create_service.rb b/app/services/tags/create_service.rb
index 329722df747..35390f5082c 100644
--- a/app/services/tags/create_service.rb
+++ b/app/services/tags/create_service.rb
@@ -7,7 +7,7 @@ module Tags
return error('Tag name invalid') unless valid_tag
repository = project.repository
- message&.strip!
+ message = message&.strip
new_tag = nil
diff --git a/app/services/todos/destroy/base_service.rb b/app/services/todos/destroy/base_service.rb
index dff5e1f30e5..aeb60e50c64 100644
--- a/app/services/todos/destroy/base_service.rb
+++ b/app/services/todos/destroy/base_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class BaseService
diff --git a/app/services/todos/destroy/confidential_issue_service.rb b/app/services/todos/destroy/confidential_issue_service.rb
index c5b66df057a..efec0f22da5 100644
--- a/app/services/todos/destroy/confidential_issue_service.rb
+++ b/app/services/todos/destroy/confidential_issue_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class ConfidentialIssueService < ::Todos::Destroy::BaseService
diff --git a/app/services/todos/destroy/entity_leave_service.rb b/app/services/todos/destroy/entity_leave_service.rb
index 045f5ecaae7..4cb9d08713d 100644
--- a/app/services/todos/destroy/entity_leave_service.rb
+++ b/app/services/todos/destroy/entity_leave_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class EntityLeaveService < ::Todos::Destroy::BaseService
diff --git a/app/services/todos/destroy/group_private_service.rb b/app/services/todos/destroy/group_private_service.rb
index d13fa7a6516..f67f1d40597 100644
--- a/app/services/todos/destroy/group_private_service.rb
+++ b/app/services/todos/destroy/group_private_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class GroupPrivateService < ::Todos::Destroy::BaseService
diff --git a/app/services/todos/destroy/private_features_service.rb b/app/services/todos/destroy/private_features_service.rb
index 4d8e2877bfb..7e204885b31 100644
--- a/app/services/todos/destroy/private_features_service.rb
+++ b/app/services/todos/destroy/private_features_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class PrivateFeaturesService < ::Todos::Destroy::BaseService
diff --git a/app/services/todos/destroy/project_private_service.rb b/app/services/todos/destroy/project_private_service.rb
index 315a0c33398..ae8fab3ffca 100644
--- a/app/services/todos/destroy/project_private_service.rb
+++ b/app/services/todos/destroy/project_private_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class ProjectPrivateService < ::Todos::Destroy::BaseService
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 7c8243a7a90..622cb11010e 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -29,5 +29,11 @@
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external
+ .form-group
+ = f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
+ .form-check
+ = f.check_box :user_show_add_ssh_key_message, class: 'form-check-input'
+ = f.label :user_show_add_ssh_key_message, class: 'form-check-label' do
+ Inform users without uploaded SSH keys that they can't push over SSH until one is added
= f.submit 'Save changes', class: 'btn btn-success'
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index 258d50ad676..6133a7646f4 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -325,6 +325,8 @@
.settings-content
= render partial: 'repository_mirrors_form'
+= render_if_exists 'admin/application_settings/templates', expanded: expanded
+
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
diff --git a/app/views/admin/spam_logs/index.html.haml b/app/views/admin/spam_logs/index.html.haml
index 8aaa6379730..b45d3e4823b 100644
--- a/app/views/admin/spam_logs/index.html.haml
+++ b/app/views/admin/spam_logs/index.html.haml
@@ -17,6 +17,6 @@
%th Primary Action
%th
= render @spam_logs
- = paginate @spam_logs
+ = paginate @spam_logs, theme: 'gitlab'
- else
%h4 There are no Spam Logs
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index f730fd05176..029efadd75d 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -128,8 +128,8 @@
.col-md-6
- unless @user == current_user
- unless @user.confirmed?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
Confirm user
.card-body
- if @user.unconfirmed_email.present?
@@ -138,8 +138,8 @@
%br
= link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
- if @user.blocked?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
This user is blocked
.card-body
%p A blocked user cannot:
@@ -162,8 +162,8 @@
%br
= link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning"
- if @user.access_locked?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
This account has been locked
.card-body
%p This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account.
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 8560b72fe85..24f256d083b 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -2,11 +2,11 @@
.file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } }
- .editor-ref
+ .editor-ref.block-truncated
= sprite_icon('fork', size: 12)
= ref
- %span.editor-file-name
- - if current_action?(:edit) || current_action?(:update)
+ - if current_action?(:edit) || current_action?(:update)
+ %span.editor-file-name
= text_field_tag 'file_path', (params[:file_path] || @path),
class: 'form-control new-file-path js-file-path-name-input'
@@ -16,7 +16,7 @@
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name js-file-path-name-input'
- .float-right.file-buttons
+ .file-buttons
= button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do
%span.no-wrap
= custom_icon('icon_no_wrap')
diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml
index 8a549d431ee..12cf40bb65f 100644
--- a/app/views/projects/forks/_fork_button.html.haml
+++ b/app/views/projects/forks/_fork_button.html.haml
@@ -5,7 +5,7 @@
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked
= link_to project_path(forked_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = project_identicon(namespace, class: "avatar s100 identicon")
+ = project_icon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
@@ -18,7 +18,7 @@
class: ("disabled has-tooltip" unless can_create_project),
title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = project_identicon(namespace, class: "avatar s100 identicon")
+ = project_icon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index 759efd4e9d4..86b2b8bf2f7 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -1,12 +1,7 @@
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.sidebar-container
.blocks-container
- - if can?(current_user, :create_build_terminal, @build)
- .block
- = link_to terminal_project_job_path(@project, @build), class: 'terminal-button pull-right btn visible-md-block visible-lg-block', title: 'Terminal' do
- Terminal
-
- #js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } }
+ #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } }
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
.block
diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml
index 64f0fde30cf..e051f9e6331 100644
--- a/app/views/projects/mirrors/_instructions.html.haml
+++ b/app/views/projects/mirrors/_instructions.html.haml
@@ -1,10 +1,11 @@
.account-well.prepend-top-default.append-bottom-default
%ul
%li
- The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>.
- %li
- Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.
- %li
- The update action will time out after 10 minutes. For big repositories, use a clone/push combination.
- %li
- The Git LFS objects will <strong>not</strong> be synced.
+ = _('The repository must be accessible over <code>http://</code>,
+ <code>https://</code>, <code>ssh://</code> and <code>git://</code>.').html_safe
+ %li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe
+ %li= _("The update action will time out after #{import_will_timeout_message(Gitlab.config.gitlab_shell.git_timeout)} minutes. For big repositories, use a clone/push combination.")
+ %li= _('The Git LFS objects will <strong>not</strong> be synced.').html_safe
+ %li
+ = _('This user will be the author of all events in the activity feed that are the result of an update,
+ like new branches being created or new commits being pushed to existing branches.')
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
new file mode 100644
index 00000000000..53387b3a50c
--- /dev/null
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -0,0 +1,63 @@
+- expanded = Rails.env.test?
+- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
+
+%section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= _('Mirroring repositories')
+ %button.btn.js-settings-toggle
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.')
+ = link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
+
+ .settings-content
+ = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f|
+ .panel.panel-default
+ .panel-heading
+ %h3.panel-title= _('Mirror a repository')
+ .panel-body
+ %div= form_errors(@project)
+
+ .form-group.has-feedback
+ = label_tag :url, _('Git repository URL'), class: 'label-light'
+ = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+"
+
+ = render 'projects/mirrors/instructions'
+
+ = render 'projects/mirrors/mirror_repos_form', f: f
+
+ .form-check.append-bottom-10
+ = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
+ = label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label'
+ = link_to icon('question-circle'), help_page_path('user/project/protected_branches')
+
+ .panel-footer
+ = f.submit _('Mirror repository'), class: 'btn btn-create', name: :update_remote_mirror
+
+ .panel.panel-default
+ .table-responsive
+ %table.table.push-pull-table
+ %thead
+ %tr
+ %th
+ = _('Mirrored repositories')
+ = render_if_exists 'projects/mirrors/mirrored_repositories_count'
+ %th= _('Direction')
+ %th= _('Last update')
+ %th
+ %th
+ %tbody.js-mirrors-table-body
+ = render_if_exists 'projects/mirrors/table_pull_row'
+ - @project.remote_mirrors.each_with_index do |mirror, index|
+ - if mirror.enabled
+ %tr
+ %td= mirror.safe_url
+ %td= _('Push')
+ %td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
+ %td
+ - if mirror.last_error.present?
+ .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
+ %td.mirror-action-buttons
+ .btn-group.mirror-actions-group.pull-right{ role: 'group' }
+ = render 'shared/remote_mirror_update_button', remote_mirror: mirror
+ %button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml
new file mode 100644
index 00000000000..93994cb30ac
--- /dev/null
+++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml
@@ -0,0 +1,18 @@
+- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
+
+.form-group
+ = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
+ = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction', disabled: true
+
+= f.fields_for :remote_mirrors, @project.remote_mirrors.build do |rm_f|
+ = rm_f.hidden_field :enabled, value: '1'
+ = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
+ = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
+
+.form-group
+ = label_tag :auth_method, _('Authentication method'), class: 'label-bold'
+ = select_tag :auth_method, options_for_select([[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method" }
+
+.form-group.js-password-group.collapse
+ = label_tag :password, _('Password'), class: 'label-bold'
+ = text_field_tag :password, '', class: 'form-control js-password'
diff --git a/app/views/projects/mirrors/_push.html.haml b/app/views/projects/mirrors/_push.html.haml
deleted file mode 100644
index 08375e09816..00000000000
--- a/app/views/projects/mirrors/_push.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-- expanded = Rails.env.test?
-%section.settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- Push to a remote repository
- %button.btn.js-settings-toggle
- = expanded ? 'Collapse' : 'Expand'
- %p
- Set up the remote repository that you want to update with the content of the current repository
- every time someone pushes to it.
- = link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository'), target: '_blank'
- .settings-content
- = form_for @project, url: project_mirror_path(@project) do |f|
- %div
- = form_errors(@project)
- = render "shared/remote_mirror_update_button", remote_mirror: @remote_mirror
- - if @remote_mirror.last_error.present?
- .panel.panel-danger
- .panel-heading
- - if @remote_mirror.last_update_at
- The remote repository failed to update #{time_ago_with_tooltip(@remote_mirror.last_update_at)}.
- - else
- The remote repository failed to update.
-
- - if @remote_mirror.last_successful_update_at
- Last successful update #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
- .panel-body
- %pre
- :preserve
- #{h(@remote_mirror.last_error.strip)}
- = f.fields_for :remote_mirrors, @remote_mirror do |rm_form|
- .form-group
- = rm_form.check_box :enabled, class: "float-left"
- .prepend-left-20
- = rm_form.label :enabled, "Remote mirror repository", class: "label-bold append-bottom-0"
- %p.light.append-bottom-0
- Automatically update the remote mirror's branches, tags, and commits from this repository every time someone pushes to it.
- .form-group.has-feedback
- = rm_form.label :url, "Git repository URL", class: "label-bold"
- = rm_form.text_field :url, class: "form-control", placeholder: 'https://username:password@gitlab.company.com/group/project.git'
-
- = render "projects/mirrors/instructions"
-
- .form-group
- = rm_form.check_box :only_protected_branches, class: 'float-left'
- .prepend-left-20
- = rm_form.label :only_protected_branches, class: 'label-bold'
- = link_to icon('question-circle'), help_page_path('user/project/protected_branches')
-
- = f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror'
diff --git a/app/views/projects/mirrors/_show.html.haml b/app/views/projects/mirrors/_show.html.haml
index de77701a373..8318d5898a1 100644
--- a/app/views/projects/mirrors/_show.html.haml
+++ b/app/views/projects/mirrors/_show.html.haml
@@ -1,3 +1 @@
-- if can?(current_user, :admin_remote_mirror, @project)
- = render 'projects/mirrors/push'
-
+= render 'projects/mirrors/mirror_repos'
diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml
index cd9177c0f9e..988dabef3a0 100644
--- a/app/views/projects/pages/_use.html.haml
+++ b/app/views/projects/pages/_use.html.haml
@@ -1,6 +1,6 @@
- unless @project.pages_deployed?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
Configure pages
.card-body
%p
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 434aed2f603..9134257b631 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -17,7 +17,7 @@
%h5.prepend-top-0
= _("Git strategy for pipelines")
%p
- = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code")
+ = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code").html_safe
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank'
.form-check
= f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' }
@@ -47,7 +47,7 @@
= f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold'
= f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
%p.form-text.text-muted
- = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>")
+ = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>").html_safe
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
%hr
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index fdcd126e7a3..a8d4d4af93a 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,4 +1,6 @@
- project = find_project_for_result_blob(blob)
+- return unless project
+
- file_name, blob = parse_search_result(blob)
- blob_link = project_blob_path(project, tree_join(blob.ref, file_name))
diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml
index 34de1c0695f..f32cff18fa8 100644
--- a/app/views/shared/_remote_mirror_update_button.html.haml
+++ b/app/views/shared/_remote_mirror_update_button.html.haml
@@ -1,13 +1,6 @@
-- if @project.has_remote_mirror?
- .append-bottom-default
- - if remote_mirror.update_in_progress?
- %span.btn.disabled
- = icon("refresh spin")
- Updating&hellip;
- - else
- = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn" do
- = icon("refresh")
- Update Now
- - if @remote_mirror.last_successful_update_at
- %p.inline.prepend-left-10
- Successfully updated #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
+- if remote_mirror.update_in_progress?
+ %button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
+ = icon("refresh spin")
+- else
+ = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
+ = icon("refresh")
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
index ee6354b1c28..ee97f0172da 100644
--- a/app/views/shared/milestones/_issuables.html.haml
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -2,8 +2,8 @@
- primary = local_assigns.fetch(:primary, false)
- panel_class = primary ? 'bg-primary text-white' : ''
-.card{ class: panel_class }
- .card-header
+.card
+ .card-header{ class: panel_class }
.title
= title
- if show_counter
diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml
index 1f6e8f98bbb..43a87fd8397 100644
--- a/app/views/shared/notifications/_custom_notifications.html.haml
+++ b/app/views/shared/notifications/_custom_notifications.html.haml
@@ -19,7 +19,7 @@
- paragraph = _('Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.') % { notification_link: notification_link.html_safe }
#{ paragraph.html_safe }
.col-lg-8
- - NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index|
+ - notification_setting.email_events.each_with_index do |event, index|
- field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"
.form-group
.form-check{ class: ("prepend-top-0" if index == 0) }
diff --git a/app/views/shared/plugins/_index.html.haml b/app/views/shared/plugins/_index.html.haml
index 7bcc54e7459..9d230d12be2 100644
--- a/app/views/shared/plugins/_index.html.haml
+++ b/app/views/shared/plugins/_index.html.haml
@@ -19,5 +19,5 @@
.monospace
= File.basename(file)
- else
- %p.card.bg-light.text-center
- No plugins found.
+ .card.bg-light.text-center
+ .nothing-here-block No plugins found.
diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml
index 0337680d79b..fa93307be31 100644
--- a/app/views/shared/runners/_form.html.haml
+++ b/app/views/shared/runners/_form.html.haml
@@ -1,56 +1,56 @@
= form_for runner, url: runner_form_url do |f|
= form_errors(runner)
.form-group.row
- = label :active, "Active", class: 'col-form-label col-sm-2'
+ = label :active, _("Active"), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :active, { class: 'form-check-input' }
- %span.light Paused Runners don't accept new jobs
+ %label.light{ for: :runner_active }= _("Paused Runners don't accept new jobs")
.form-group.row
- = label :protected, "Protected", class: 'col-form-label col-sm-2'
+ = label :protected, _("Protected"), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :access_level, { class: 'form-check-input' }, 'ref_protected', 'not_protected'
- %span.light This runner will only run on pipelines triggered on protected branches
+ %label.light{ for: :runner_access_level }= _('This runner will only run on pipelines triggered on protected branches')
.form-group.row
- = label :run_untagged, 'Run untagged jobs', class: 'col-form-label col-sm-2'
+ = label :run_untagged, _('Run untagged jobs'), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :run_untagged, { class: 'form-check-input' }
- %span.light Indicates whether this runner can pick jobs without tags
+ %label.light{ for: :runner_run_untagged }= _('Indicates whether this runner can pick jobs without tags')
- unless runner.group_type?
.form-group.row
= label :locked, _('Lock to current projects'), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :locked, { class: 'form-check-input' }
- %span.light= _('When a runner is locked, it cannot be assigned to other projects')
+ %label.light{ for: :runner_locked }= _('When a runner is locked, it cannot be assigned to other projects')
.form-group.row
= label_tag :token, class: 'col-form-label col-sm-2' do
- Token
+ = _('Token')
.col-sm-10
= f.text_field :token, class: 'form-control', readonly: true
.form-group.row
= label_tag :ip_address, class: 'col-form-label col-sm-2' do
- IP Address
+ = _('IP Address')
.col-sm-10
= f.text_field :ip_address, class: 'form-control', readonly: true
.form-group.row
= label_tag :description, class: 'col-form-label col-sm-2' do
- Description
+ = _('Description')
.col-sm-10
= f.text_field :description, class: 'form-control'
.form-group.row
= label_tag :maximum_timeout_human_readable, class: 'col-form-label col-sm-2' do
- Maximum job timeout
+ = _('Maximum job timeout')
.col-sm-10
= f.text_field :maximum_timeout_human_readable, class: 'form-control'
- .form-text.text-muted This timeout will take precedence when lower than Project-defined timeout
+ .form-text.text-muted= _('This timeout will take precedence when lower than Project-defined timeout')
.form-group.row
= label_tag :tag_list, class: 'col-form-label col-sm-2' do
- Tags
+ = _('Tags')
.col-sm-10
= f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control'
- .form-text.text-muted You can setup jobs to only use Runners with specific tags. Separate tags with commas.
+ .form-text.text-muted= _('You can setup jobs to only use Runners with specific tags. Separate tags with commas.')
.form-actions
- = f.submit 'Save changes', class: 'btn btn-save'
+ = f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb
index 537b8fd5963..cfbbbffc8a6 100644
--- a/app/workers/detect_repository_languages_worker.rb
+++ b/app/workers/detect_repository_languages_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DetectRepositoryLanguagesWorker
include ApplicationWorker
include ExceptionBacktrace
diff --git a/app/workers/todos_destroyer/confidential_issue_worker.rb b/app/workers/todos_destroyer/confidential_issue_worker.rb
index 9d640c14963..481fde8c83d 100644
--- a/app/workers/todos_destroyer/confidential_issue_worker.rb
+++ b/app/workers/todos_destroyer/confidential_issue_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class ConfidentialIssueWorker
include ApplicationWorker
diff --git a/app/workers/todos_destroyer/entity_leave_worker.rb b/app/workers/todos_destroyer/entity_leave_worker.rb
index e62d9876f4a..7db3f6c84b4 100644
--- a/app/workers/todos_destroyer/entity_leave_worker.rb
+++ b/app/workers/todos_destroyer/entity_leave_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class EntityLeaveWorker
include ApplicationWorker
diff --git a/app/workers/todos_destroyer/group_private_worker.rb b/app/workers/todos_destroyer/group_private_worker.rb
index 3e47eec7461..21ec4abe478 100644
--- a/app/workers/todos_destroyer/group_private_worker.rb
+++ b/app/workers/todos_destroyer/group_private_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class GroupPrivateWorker
include ApplicationWorker
diff --git a/app/workers/todos_destroyer/private_features_worker.rb b/app/workers/todos_destroyer/private_features_worker.rb
index f457d5e0471..1e68f0fd5ae 100644
--- a/app/workers/todos_destroyer/private_features_worker.rb
+++ b/app/workers/todos_destroyer/private_features_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class PrivateFeaturesWorker
include ApplicationWorker
diff --git a/app/workers/todos_destroyer/project_private_worker.rb b/app/workers/todos_destroyer/project_private_worker.rb
index 7a853c36370..064e001530c 100644
--- a/app/workers/todos_destroyer/project_private_worker.rb
+++ b/app/workers/todos_destroyer/project_private_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class ProjectPrivateWorker
include ApplicationWorker
diff --git a/bin/secpick b/bin/secpick
index 5029fe57cfe..5e30c8e72c5 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -35,7 +35,9 @@ parser.parse!
abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
-branch = [BRANCH_PREFIX, options[:branch], options[:version]].join('-').freeze
+branch = "#{options[:branch]}-#{options[:version]}"
+branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-")
+branch = branch.freeze
stable_branch = "#{BRANCH_PREFIX}-#{options[:version]}".freeze
command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch}"
diff --git a/changelogs/unreleased/25990-web-terminal-improvements.yml b/changelogs/unreleased/25990-web-terminal-improvements.yml
new file mode 100644
index 00000000000..99a4a82ea66
--- /dev/null
+++ b/changelogs/unreleased/25990-web-terminal-improvements.yml
@@ -0,0 +1,5 @@
+---
+title: Make terminal button more visible
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/2747-protected-environments-backend-ce.yml b/changelogs/unreleased/2747-protected-environments-backend-ce.yml
new file mode 100644
index 00000000000..dcec74a33a7
--- /dev/null
+++ b/changelogs/unreleased/2747-protected-environments-backend-ce.yml
@@ -0,0 +1,5 @@
+---
+title: CE Port of Protected Environments backend
+merge_request: 20859
+author:
+type: other
diff --git a/changelogs/unreleased/28930-add-project-reference-filter.yml b/changelogs/unreleased/28930-add-project-reference-filter.yml
new file mode 100644
index 00000000000..c7679c5fe76
--- /dev/null
+++ b/changelogs/unreleased/28930-add-project-reference-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Add the ability to reference projects in comments and other markdown text.
+merge_request: 20285
+author: Reuben Pereira
+type: added
diff --git a/changelogs/unreleased/41996-copy-to-clipboard-tooltip-appears-under-modal.yml b/changelogs/unreleased/41996-copy-to-clipboard-tooltip-appears-under-modal.yml
new file mode 100644
index 00000000000..e452a91d8b7
--- /dev/null
+++ b/changelogs/unreleased/41996-copy-to-clipboard-tooltip-appears-under-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Solve tooltip appears under modal
+merge_request: 21017
+author:
+type: fixed
diff --git a/changelogs/unreleased/45663-tag-quick-action-on-commit-comments.yml b/changelogs/unreleased/45663-tag-quick-action-on-commit-comments.yml
new file mode 100644
index 00000000000..6d664511e6e
--- /dev/null
+++ b/changelogs/unreleased/45663-tag-quick-action-on-commit-comments.yml
@@ -0,0 +1,5 @@
+---
+title: "`/tag` quick action on Commit comments"
+merge_request: 20694
+author: Peter Leitzen
+type: added
diff --git a/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml b/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml
new file mode 100644
index 00000000000..81ca632947d
--- /dev/null
+++ b/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml
@@ -0,0 +1,5 @@
+---
+title: Fix buttons on the new file page wrapping outside of the container
+merge_request: 21015
+author:
+type: fixed
diff --git a/changelogs/unreleased/48320-cancel-a-created-job.yml b/changelogs/unreleased/48320-cancel-a-created-job.yml
new file mode 100644
index 00000000000..3e7a9e9ae52
--- /dev/null
+++ b/changelogs/unreleased/48320-cancel-a-created-job.yml
@@ -0,0 +1,5 @@
+---
+title: Allows to cancel a Created job
+merge_request: 20635
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/48942-rename-backlog-list-to-open-issue-boards.yml b/changelogs/unreleased/48942-rename-backlog-list-to-open-issue-boards.yml
new file mode 100644
index 00000000000..26851fc2dec
--- /dev/null
+++ b/changelogs/unreleased/48942-rename-backlog-list-to-open-issue-boards.yml
@@ -0,0 +1,5 @@
+---
+title: Change 'Backlog' list title to 'Open' in Issue Boards
+merge_request: 21131
+author:
+type: changed
diff --git a/changelogs/unreleased/48967-disable-statement-timeout.yml b/changelogs/unreleased/48967-disable-statement-timeout.yml
new file mode 100644
index 00000000000..2da800ed41e
--- /dev/null
+++ b/changelogs/unreleased/48967-disable-statement-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: disable_statement_timeout no longer leak to other migrations
+merge_request: 20503
+author:
+type: fixed
diff --git a/changelogs/unreleased/49905-fix-checkboxes-runners.yml b/changelogs/unreleased/49905-fix-checkboxes-runners.yml
new file mode 100644
index 00000000000..af40e5348b8
--- /dev/null
+++ b/changelogs/unreleased/49905-fix-checkboxes-runners.yml
@@ -0,0 +1,5 @@
+---
+title: Fix checkboxes on runner admin settings - The labels are now clickable
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/49953-add-user_show_add_ssh_key_message-setting.yml b/changelogs/unreleased/49953-add-user_show_add_ssh_key_message-setting.yml
new file mode 100644
index 00000000000..82423092792
--- /dev/null
+++ b/changelogs/unreleased/49953-add-user_show_add_ssh_key_message-setting.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to suppress the global "You won't be able to use SSH" message
+merge_request: 21027
+author: Ævar Arnfjörð Bjarmason
+type: added
diff --git a/changelogs/unreleased/50047-spam-logs-pagination.yml b/changelogs/unreleased/50047-spam-logs-pagination.yml
new file mode 100644
index 00000000000..ca5f432cd8c
--- /dev/null
+++ b/changelogs/unreleased/50047-spam-logs-pagination.yml
@@ -0,0 +1,5 @@
+---
+title: Add gitlab theme to spam logs pagination
+merge_request: 21145
+author:
+type: fixed
diff --git a/changelogs/unreleased/50063-add-missing-i18n-strings-to-issue-boards.yml b/changelogs/unreleased/50063-add-missing-i18n-strings-to-issue-boards.yml
new file mode 100644
index 00000000000..ca17a41d611
--- /dev/null
+++ b/changelogs/unreleased/50063-add-missing-i18n-strings-to-issue-boards.yml
@@ -0,0 +1,5 @@
+---
+title: Added missing i18n strings to issue boards lables dropdown
+merge_request: 21081
+author:
+type: other
diff --git a/changelogs/unreleased/50101-aritfacts-block.yml b/changelogs/unreleased/50101-aritfacts-block.yml
new file mode 100644
index 00000000000..435e9d9d486
--- /dev/null
+++ b/changelogs/unreleased/50101-aritfacts-block.yml
@@ -0,0 +1,5 @@
+---
+title: Creates Vue component for artifacts block on job page
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50101-erased-block.yml b/changelogs/unreleased/50101-erased-block.yml
new file mode 100644
index 00000000000..5a5c9bc0fc4
--- /dev/null
+++ b/changelogs/unreleased/50101-erased-block.yml
@@ -0,0 +1,5 @@
+---
+title: Creates vue component for erased block on job view
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50101-job-log-component.yml b/changelogs/unreleased/50101-job-log-component.yml
new file mode 100644
index 00000000000..0759e7cfbd9
--- /dev/null
+++ b/changelogs/unreleased/50101-job-log-component.yml
@@ -0,0 +1,5 @@
+---
+title: Creates vue component for job log trace
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50101-stuck-component.yml b/changelogs/unreleased/50101-stuck-component.yml
new file mode 100644
index 00000000000..bfe4009a2b3
--- /dev/null
+++ b/changelogs/unreleased/50101-stuck-component.yml
@@ -0,0 +1,5 @@
+---
+title: Creates Vvue component for warning block about stuck runners
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50126-blocked-user-card.yml b/changelogs/unreleased/50126-blocked-user-card.yml
new file mode 100644
index 00000000000..a42d62e5530
--- /dev/null
+++ b/changelogs/unreleased/50126-blocked-user-card.yml
@@ -0,0 +1,5 @@
+---
+title: Fix blocked user card style
+merge_request: 21095
+author:
+type: fixed
diff --git a/changelogs/unreleased/50180-fa-icon-google-audit.yml b/changelogs/unreleased/50180-fa-icon-google-audit.yml
new file mode 100644
index 00000000000..fb1771a7570
--- /dev/null
+++ b/changelogs/unreleased/50180-fa-icon-google-audit.yml
@@ -0,0 +1,5 @@
+---
+title: Show google icon in audit log
+merge_request: 21207
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/50243-auto-devops-behind-a-proxy.yml b/changelogs/unreleased/50243-auto-devops-behind-a-proxy.yml
new file mode 100644
index 00000000000..0f6208d0c7a
--- /dev/null
+++ b/changelogs/unreleased/50243-auto-devops-behind-a-proxy.yml
@@ -0,0 +1,6 @@
+---
+title: Vendor Auto-DevOps.gitlab-ci.yml with new proxy env vars passed through to
+ docker
+merge_request: 21159
+author: kinolaev
+type: added
diff --git a/changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml b/changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml
new file mode 100644
index 00000000000..e081dfe6093
--- /dev/null
+++ b/changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml
@@ -0,0 +1,5 @@
+---
+title: 'Auto-DevOps.gitlab-ci.yml: Update glibc package signing key URL'
+merge_request: 21182
+author: sgerrand
+type: fixed
diff --git a/changelogs/unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml b/changelogs/unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml
new file mode 100644
index 00000000000..eb20e34c466
--- /dev/null
+++ b/changelogs/unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken JavaScript in IE11
+merge_request: 21214
+author:
+type: fixed
diff --git a/changelogs/unreleased/add_google_noto_color_emoji_font.yml b/changelogs/unreleased/add_google_noto_color_emoji_font.yml
new file mode 100644
index 00000000000..9ba46262767
--- /dev/null
+++ b/changelogs/unreleased/add_google_noto_color_emoji_font.yml
@@ -0,0 +1,5 @@
+---
+title: Add Noto Color Emoji font support
+merge_request: 19036
+author: Alexander Popov
+type: changed
diff --git a/changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml b/changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml
new file mode 100644
index 00000000000..a1625193189
--- /dev/null
+++ b/changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml
@@ -0,0 +1,5 @@
+---
+title: 'Auto-DevOps.gitlab-ci.yml: update glibc package to 2.28'
+merge_request: 21191
+author: sgerrand
+type: fixed
diff --git a/changelogs/unreleased/bvl-add-czech.yml b/changelogs/unreleased/bvl-add-czech.yml
new file mode 100644
index 00000000000..49e0e4a74b7
--- /dev/null
+++ b/changelogs/unreleased/bvl-add-czech.yml
@@ -0,0 +1,5 @@
+---
+title: Add Czech as an available language.
+merge_request: 21201
+author:
+type: added
diff --git a/changelogs/unreleased/bvl-merge-base-api.yml b/changelogs/unreleased/bvl-merge-base-api.yml
new file mode 100644
index 00000000000..78fb3ce0897
--- /dev/null
+++ b/changelogs/unreleased/bvl-merge-base-api.yml
@@ -0,0 +1,5 @@
+---
+title: Get the merge base of two refs through the API
+merge_request: 20929
+author:
+type: added
diff --git a/changelogs/unreleased/ce-5666-optimize_querying_manageable_groups.yml b/changelogs/unreleased/ce-5666-optimize_querying_manageable_groups.yml
new file mode 100644
index 00000000000..0c6a1071cdd
--- /dev/null
+++ b/changelogs/unreleased/ce-5666-optimize_querying_manageable_groups.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize querying User#manageable_groups
+merge_request: 21050
+author:
+type: performance
diff --git a/changelogs/unreleased/expose-all-artifacts-sizes-in-jobs-api.yml b/changelogs/unreleased/expose-all-artifacts-sizes-in-jobs-api.yml
new file mode 100644
index 00000000000..1453d39934b
--- /dev/null
+++ b/changelogs/unreleased/expose-all-artifacts-sizes-in-jobs-api.yml
@@ -0,0 +1,5 @@
+---
+title: Expose all artifacts sizes in jobs api
+merge_request: 20821
+author: Peter Marko
+type: added
diff --git a/changelogs/unreleased/feat-add-default-avatar-to-group.yml b/changelogs/unreleased/feat-add-default-avatar-to-group.yml
new file mode 100644
index 00000000000..56d8f2ccd6d
--- /dev/null
+++ b/changelogs/unreleased/feat-add-default-avatar-to-group.yml
@@ -0,0 +1,5 @@
+---
+title: Add default avatar to group
+merge_request: 17271
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/fix-labels-list-item-height-with-no-description.yml b/changelogs/unreleased/fix-labels-list-item-height-with-no-description.yml
new file mode 100644
index 00000000000..d215d034917
--- /dev/null
+++ b/changelogs/unreleased/fix-labels-list-item-height-with-no-description.yml
@@ -0,0 +1,5 @@
+---
+title: Fix label list item container height when there is no label description
+merge_request: 21106
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-pipeline-fixture-seeder.yml b/changelogs/unreleased/fix-pipeline-fixture-seeder.yml
new file mode 100644
index 00000000000..02b83062e07
--- /dev/null
+++ b/changelogs/unreleased/fix-pipeline-fixture-seeder.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pipeline fixture seeder
+merge_request: 21088
+author:
+type: fixed
diff --git a/changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml b/changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml
new file mode 100644
index 00000000000..068681dfe19
--- /dev/null
+++ b/changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce differences between CE and EE code base in reports components
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/frozen-string-enable-app-mailers.yml b/changelogs/unreleased/frozen-string-enable-app-mailers.yml
new file mode 100644
index 00000000000..2cd247ca76c
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-enable-app-mailers.yml
@@ -0,0 +1,5 @@
+---
+title: Enable frozen in app/mailers/**/*.rb
+merge_request: 21147
+author: gfyoung
+type: performance
diff --git a/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml b/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml
new file mode 100644
index 00000000000..a77f3baeed3
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml
@@ -0,0 +1,5 @@
+---
+title: Enable frozen string in rest of app/models/**/*.rb
+merge_request: gfyoung
+author:
+type: performance
diff --git a/changelogs/unreleased/frozen-string-enable-app-vestigial.yml b/changelogs/unreleased/frozen-string-enable-app-vestigial.yml
new file mode 100644
index 00000000000..8cb7bd43784
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-enable-app-vestigial.yml
@@ -0,0 +1,5 @@
+---
+title: Enable frozen string in vestigial app files
+merge_request:
+author: gfyoung
+type: performance
diff --git a/changelogs/unreleased/gitaly-install-path.yml b/changelogs/unreleased/gitaly-install-path.yml
new file mode 100644
index 00000000000..4b24cd81dc7
--- /dev/null
+++ b/changelogs/unreleased/gitaly-install-path.yml
@@ -0,0 +1,5 @@
+---
+title: Remove storage path dependency of gitaly install task
+merge_request: 21101
+author:
+type: changed
diff --git a/changelogs/unreleased/ide-delete-new-files-state.yml b/changelogs/unreleased/ide-delete-new-files-state.yml
new file mode 100644
index 00000000000..500115d19d0
--- /dev/null
+++ b/changelogs/unreleased/ide-delete-new-files-state.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed IDE deleting new files creating wrong state
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ide-header-buttons-tooltip.yml b/changelogs/unreleased/ide-header-buttons-tooltip.yml
new file mode 100644
index 00000000000..4c8f6fd554f
--- /dev/null
+++ b/changelogs/unreleased/ide-header-buttons-tooltip.yml
@@ -0,0 +1,5 @@
+---
+title: Added tooltips to tree list header
+merge_request: 21138
+author:
+type: added
diff --git a/changelogs/unreleased/ide-open-empty-merge-request.yml b/changelogs/unreleased/ide-open-empty-merge-request.yml
new file mode 100644
index 00000000000..05f2de5d31c
--- /dev/null
+++ b/changelogs/unreleased/ide-open-empty-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Fix empty merge requests not opening in the Web IDE
+merge_request: 21102
+author:
+type: fixed
diff --git a/changelogs/unreleased/mk-bump-rainbow-gem.yml b/changelogs/unreleased/mk-bump-rainbow-gem.yml
new file mode 100644
index 00000000000..31c003fb4d9
--- /dev/null
+++ b/changelogs/unreleased/mk-bump-rainbow-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bin/secpick error and security branch prefixing
+merge_request: 21210
+author:
+type: fixed
diff --git a/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml b/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml
new file mode 100644
index 00000000000..bcf3d2c8e16
--- /dev/null
+++ b/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml
@@ -0,0 +1,6 @@
+---
+title: Combines emoji award spec files into single user_interacts_with_awards_in_issue_spec.rb
+ file
+merge_request: 21126
+author: Nate Geslin
+type: other
diff --git a/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml b/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml
new file mode 100644
index 00000000000..e31768773b1
--- /dev/null
+++ b/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml
@@ -0,0 +1,5 @@
+---
+title: Rails5 fix specs duplicate key value violates unique constraint 'index_gpg_signatures_on_commit_sha'
+merge_request: 21119
+author: Jasper Maes
+type: fixed
diff --git a/changelogs/unreleased/sh-bump-rugged-0-27-4.yml b/changelogs/unreleased/sh-bump-rugged-0-27-4.yml
new file mode 100644
index 00000000000..50373cb81ad
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-rugged-0-27-4.yml
@@ -0,0 +1,5 @@
+---
+title: Bump rugged to 0.27.4 for security fixes
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/tz-mr-incremental-rendering.yml b/changelogs/unreleased/tz-mr-incremental-rendering.yml
new file mode 100644
index 00000000000..a35fa200363
--- /dev/null
+++ b/changelogs/unreleased/tz-mr-incremental-rendering.yml
@@ -0,0 +1,4 @@
+title: Incremental rendering with Vue on merge request page
+merge_request: 21063
+author:
+type: performance
diff --git a/changelogs/unreleased/visual-improvements-language-bar.yml b/changelogs/unreleased/visual-improvements-language-bar.yml
new file mode 100644
index 00000000000..23cae22b962
--- /dev/null
+++ b/changelogs/unreleased/visual-improvements-language-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Improve visuals of language bar on projects
+merge_request: 21006
+author:
+type: changed
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index d3a63aa2a78..5535c4a14e5 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -41,7 +41,7 @@ class Gitlab::Seeder::Pipelines
when: 'manual', status: :skipped },
# notify stage
- { name: 'slack', stage: 'notify', when: 'manual', status: :created },
+ { name: 'slack', stage: 'notify', when: 'manual', status: :success },
]
EXTERNAL_JOBS = [
{ name: 'jenkins', stage: 'test', status: :success,
@@ -54,16 +54,10 @@ class Gitlab::Seeder::Pipelines
def seed!
pipelines.each do |pipeline|
- begin
- BUILDS.each { |opts| build_create!(pipeline, opts) }
- EXTERNAL_JOBS.each { |opts| commit_status_create!(pipeline, opts) }
- print '.'
- rescue ActiveRecord::RecordInvalid
- print 'F'
- ensure
- pipeline.update_duration
- pipeline.update_status
- end
+ BUILDS.each { |opts| build_create!(pipeline, opts) }
+ EXTERNAL_JOBS.each { |opts| commit_status_create!(pipeline, opts) }
+ pipeline.update_duration
+ pipeline.update_status
end
end
@@ -87,7 +81,9 @@ class Gitlab::Seeder::Pipelines
branch = merge_request.source_branch
merge_request.commits.last(4).map do |commit|
- create_pipeline!(project, branch, commit)
+ create_pipeline!(project, branch, commit).tap do |pipeline|
+ merge_request.update!(head_pipeline_id: pipeline.id)
+ end
end
end
@@ -98,7 +94,7 @@ class Gitlab::Seeder::Pipelines
def create_pipeline!(project, ref, commit)
- project.pipelines.create(sha: commit.id, ref: ref, source: :push)
+ project.pipelines.create!(sha: commit.id, ref: ref, source: :push)
end
def build_create!(pipeline, opts = {})
@@ -111,24 +107,39 @@ class Gitlab::Seeder::Pipelines
# block directly to `Ci::Build#create!`.
setup_artifacts(build)
+ setup_test_reports(build)
setup_build_log(build)
build.project.environments.
find_or_create_by(name: build.expanded_environment_name)
- build.save
+ build.save!
end
end
def setup_artifacts(build)
- return unless %w[build test].include?(build.stage)
+ return unless build.stage == "build"
artifacts_cache_file(artifacts_archive_path) do |file|
- build.job_artifacts.build(project: build.project, file_type: :archive, file: file)
+ build.job_artifacts.build(project: build.project, file_type: :archive, file_format: :zip, file: file)
end
artifacts_cache_file(artifacts_metadata_path) do |file|
- build.job_artifacts.build(project: build.project, file_type: :metadata, file: file)
+ build.job_artifacts.build(project: build.project, file_type: :metadata, file_format: :gzip, file: file)
+ end
+ end
+
+ def setup_test_reports(build)
+ return unless build.stage == "test" && build.name == "rspec:osx"
+
+ if build.ref == build.project.default_branch
+ artifacts_cache_file(test_reports_pass_path) do |file|
+ build.job_artifacts.build(project: build.project, file_type: :junit, file_format: :gzip, file: file)
+ end
+ else
+ artifacts_cache_file(test_reports_failed_path) do |file|
+ build.job_artifacts.build(project: build.project, file_type: :junit, file_format: :gzip, file: file)
+ end
end
end
@@ -171,13 +182,21 @@ class Gitlab::Seeder::Pipelines
Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
end
+ def test_reports_pass_path
+ Rails.root + 'spec/fixtures/junit/junit_ant.xml.gz'
+ end
+
+ def test_reports_failed_path
+ Rails.root + 'spec/fixtures/junit/junit.xml.gz'
+ end
+
def artifacts_cache_file(file_path)
- cache_path = file_path.to_s.gsub('ci_', "p#{@project.id}_")
+ file = Tempfile.new("artifacts")
+ file.close
- FileUtils.copy(file_path, cache_path)
- File.open(cache_path) do |file|
- yield file
- end
+ FileUtils.copy(file_path, file.path)
+
+ yield(UploadedFile.new(file.path, filename: File.basename(file_path)))
end
end
diff --git a/db/fixtures/development/23_spam_logs.rb b/db/fixtures/development/23_spam_logs.rb
new file mode 100644
index 00000000000..81cc13e6b2d
--- /dev/null
+++ b/db/fixtures/development/23_spam_logs.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Db
+ module Fixtures
+ module Development
+ class SpamLog
+ def self.seed
+ Gitlab::Seeder.quiet do
+ (::SpamLog.default_per_page + 3).times do |i|
+ ::SpamLog.create(
+ user: self.random_user,
+ user_agent: FFaker::Lorem.sentence,
+ source_ip: FFaker::Internet.ip_v4_address,
+ title: FFaker::Lorem.sentence,
+ description: FFaker::Lorem.paragraph,
+ via_api: FFaker::Boolean.random,
+ submitted_as_ham: FFaker::Boolean.random,
+ recaptcha_verified: FFaker::Boolean.random)
+ print '.'
+ end
+ end
+ end
+
+ def self.random_user
+ User.find(User.pluck(:id).sample)
+ end
+ end
+ end
+ end
+end
+
+Db::Fixtures::Development::SpamLog.seed
diff --git a/db/migrate/20160317092222_add_moved_to_to_issue.rb b/db/migrate/20160317092222_add_moved_to_to_issue.rb
index 461e7fb3a9b..2bf549d7ecd 100644
--- a/db/migrate/20160317092222_add_moved_to_to_issue.rb
+++ b/db/migrate/20160317092222_add_moved_to_to_issue.rb
@@ -1,5 +1,5 @@
class AddMovedToToIssue < ActiveRecord::Migration
def change
- add_reference :issues, :moved_to, references: :issues
+ add_reference :issues, :moved_to, references: :issues # rubocop:disable Migration/AddReference
end
end
diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
index 1199073ed3a..12352d98a62 100644
--- a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
+++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
@@ -106,11 +106,11 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
# Disables statement timeouts for the current connection. This is
# necessary as removing of orphaned data might otherwise exceed the
# statement timeout.
- disable_statement_timeout
+ disable_statement_timeout do
+ remove_orphans(*queue.pop) until queue.empty?
- remove_orphans(*queue.pop) until queue.empty?
-
- steal_from_queues(queues - [queue])
+ steal_from_queues(queues - [queue])
+ end
end
end
end
diff --git a/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb b/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb
index db60c2087b9..a770ff63b4e 100644
--- a/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb
+++ b/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb
@@ -25,8 +25,9 @@ class AddLowerPathIndexToRedirectRoutes < ActiveRecord::Migration
# trivial to write a query that checks for an index. BUT there is a
# convenient `IF EXISTS` parameter for `DROP INDEX`.
if supports_drop_index_concurrently?
- disable_statement_timeout
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
+ disable_statement_timeout do
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
+ end
else
execute "DROP INDEX IF EXISTS #{INDEX_NAME};"
end
diff --git a/db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb b/db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb
index d1a039ed551..130b24fe6f0 100644
--- a/db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb
+++ b/db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb
@@ -8,25 +8,25 @@ class AddIndexOnNamespacesLowerName < ActiveRecord::Migration
def up
return unless Gitlab::Database.postgresql?
- disable_statement_timeout
-
- if Gitlab::Database.version.to_f >= 9.5
- # Allow us to hot-patch the index manually ahead of the migration
- execute "CREATE INDEX CONCURRENTLY IF NOT EXISTS #{INDEX_NAME} ON namespaces (lower(name));"
- else
- execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON namespaces (lower(name));"
+ disable_statement_timeout do
+ if Gitlab::Database.version.to_f >= 9.5
+ # Allow us to hot-patch the index manually ahead of the migration
+ execute "CREATE INDEX CONCURRENTLY IF NOT EXISTS #{INDEX_NAME} ON namespaces (lower(name));"
+ else
+ execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON namespaces (lower(name));"
+ end
end
end
def down
return unless Gitlab::Database.postgresql?
- disable_statement_timeout
-
- if Gitlab::Database.version.to_f >= 9.2
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
- else
- execute "DROP INDEX IF EXISTS #{INDEX_NAME};"
+ disable_statement_timeout do
+ if Gitlab::Database.version.to_f >= 9.2
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
+ else
+ execute "DROP INDEX IF EXISTS #{INDEX_NAME};"
+ end
end
end
end
diff --git a/db/migrate/20180113220114_rework_redirect_routes_indexes.rb b/db/migrate/20180113220114_rework_redirect_routes_indexes.rb
index ab9971be074..53f82a31203 100644
--- a/db/migrate/20180113220114_rework_redirect_routes_indexes.rb
+++ b/db/migrate/20180113220114_rework_redirect_routes_indexes.rb
@@ -18,51 +18,51 @@ class ReworkRedirectRoutesIndexes < ActiveRecord::Migration
OLD_INDEX_NAME_PATH_LOWER = "index_on_redirect_routes_lower_path"
def up
- disable_statement_timeout
-
- # this is a plain btree on a single boolean column. It'll never be
- # selective enough to be valuable. This class is called by
- # setup_postgresql.rake so it needs to be able to handle this
- # index not existing.
- if index_exists?(:redirect_routes, :permanent)
- remove_concurrent_index(:redirect_routes, :permanent)
- end
+ disable_statement_timeout do
+ # this is a plain btree on a single boolean column. It'll never be
+ # selective enough to be valuable. This class is called by
+ # setup_postgresql.rake so it needs to be able to handle this
+ # index not existing.
+ if index_exists?(:redirect_routes, :permanent)
+ remove_concurrent_index(:redirect_routes, :permanent)
+ end
- # If we're on MySQL then the existing index on path is ok. But on
- # Postgres we need to clean things up:
- return unless Gitlab::Database.postgresql?
+ # If we're on MySQL then the existing index on path is ok. But on
+ # Postgres we need to clean things up:
+ break unless Gitlab::Database.postgresql?
- if_not_exists = Gitlab::Database.version.to_f >= 9.5 ? "IF NOT EXISTS" : ""
+ if_not_exists = Gitlab::Database.version.to_f >= 9.5 ? "IF NOT EXISTS" : ""
- # Unique index on lower(path) across both types of redirect_routes:
- execute("CREATE UNIQUE INDEX CONCURRENTLY #{if_not_exists} #{INDEX_NAME_UNIQUE} ON redirect_routes (lower(path) varchar_pattern_ops);")
+ # Unique index on lower(path) across both types of redirect_routes:
+ execute("CREATE UNIQUE INDEX CONCURRENTLY #{if_not_exists} #{INDEX_NAME_UNIQUE} ON redirect_routes (lower(path) varchar_pattern_ops);")
- # Make two indexes on path -- one for permanent and one for temporary routes:
- execute("CREATE INDEX CONCURRENTLY #{if_not_exists} #{INDEX_NAME_PERM} ON redirect_routes (lower(path) varchar_pattern_ops) where (permanent);")
- execute("CREATE INDEX CONCURRENTLY #{if_not_exists} #{INDEX_NAME_TEMP} ON redirect_routes (lower(path) varchar_pattern_ops) where (not permanent or permanent is null) ;")
+ # Make two indexes on path -- one for permanent and one for temporary routes:
+ execute("CREATE INDEX CONCURRENTLY #{if_not_exists} #{INDEX_NAME_PERM} ON redirect_routes (lower(path) varchar_pattern_ops) where (permanent);")
+ execute("CREATE INDEX CONCURRENTLY #{if_not_exists} #{INDEX_NAME_TEMP} ON redirect_routes (lower(path) varchar_pattern_ops) where (not permanent or permanent is null) ;")
- # Remove the old indexes:
+ # Remove the old indexes:
- # This one needed to be on lower(path) but wasn't so it's replaced with the two above
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{OLD_INDEX_NAME_PATH_TPOPS};"
+ # This one needed to be on lower(path) but wasn't so it's replaced with the two above
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{OLD_INDEX_NAME_PATH_TPOPS};"
- # This one isn't needed because we only ever do = and LIKE on this
- # column so the varchar_pattern_ops index is sufficient
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{OLD_INDEX_NAME_PATH_LOWER};"
+ # This one isn't needed because we only ever do = and LIKE on this
+ # column so the varchar_pattern_ops index is sufficient
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{OLD_INDEX_NAME_PATH_LOWER};"
+ end
end
def down
- disable_statement_timeout
+ disable_statement_timeout do
+ add_concurrent_index(:redirect_routes, :permanent)
- add_concurrent_index(:redirect_routes, :permanent)
+ break unless Gitlab::Database.postgresql?
- return unless Gitlab::Database.postgresql?
+ execute("CREATE INDEX CONCURRENTLY #{OLD_INDEX_NAME_PATH_TPOPS} ON redirect_routes (path varchar_pattern_ops);")
+ execute("CREATE INDEX CONCURRENTLY #{OLD_INDEX_NAME_PATH_LOWER} ON redirect_routes (LOWER(path));")
- execute("CREATE INDEX CONCURRENTLY #{OLD_INDEX_NAME_PATH_TPOPS} ON redirect_routes (path varchar_pattern_ops);")
- execute("CREATE INDEX CONCURRENTLY #{OLD_INDEX_NAME_PATH_LOWER} ON redirect_routes (LOWER(path));")
-
- execute("DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_UNIQUE};")
- execute("DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_PERM};")
- execute("DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_TEMP};")
+ execute("DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_UNIQUE};")
+ execute("DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_PERM};")
+ execute("DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_TEMP};")
+ end
end
end
diff --git a/db/migrate/20180403035759_create_project_ci_cd_settings.rb b/db/migrate/20180403035759_create_project_ci_cd_settings.rb
index 06856af6204..173e662cffc 100644
--- a/db/migrate/20180403035759_create_project_ci_cd_settings.rb
+++ b/db/migrate/20180403035759_create_project_ci_cd_settings.rb
@@ -13,16 +13,16 @@ class CreateProjectCiCdSettings < ActiveRecord::Migration
end
end
- disable_statement_timeout
+ disable_statement_timeout do
+ # This particular INSERT will take between 10 and 20 seconds.
+ execute 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects'
- # This particular INSERT will take between 10 and 20 seconds.
- execute 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects'
+ # We add the index and foreign key separately so the above INSERT statement
+ # takes as little time as possible.
+ add_concurrent_index(:project_ci_cd_settings, :project_id, unique: true)
- # We add the index and foreign key separately so the above INSERT statement
- # takes as little time as possible.
- add_concurrent_index(:project_ci_cd_settings, :project_id, unique: true)
-
- add_foreign_key_with_retry
+ add_foreign_key_with_retry
+ end
end
def down
diff --git a/db/migrate/20180420010616_cleanup_build_stage_migration.rb b/db/migrate/20180420010616_cleanup_build_stage_migration.rb
index 24777294101..5e9fe756efd 100644
--- a/db/migrate/20180420010616_cleanup_build_stage_migration.rb
+++ b/db/migrate/20180420010616_cleanup_build_stage_migration.rb
@@ -14,48 +14,50 @@ class CleanupBuildStageMigration < ActiveRecord::Migration
end
def up
- disable_statement_timeout
-
- ##
- # We steal from the background migrations queue to catch up with the
- # scheduled migrations set.
- #
- Gitlab::BackgroundMigration.steal('MigrateBuildStage')
-
- ##
- # We add temporary index, to make iteration over batches more performant.
- # Conditional here is to avoid the need of doing that in a separate
- # migration file to make this operation idempotent.
- #
- unless index_exists_by_name?(:ci_builds, TMP_INDEX)
- add_concurrent_index(:ci_builds, :id, where: 'stage_id IS NULL', name: TMP_INDEX)
- end
-
- ##
- # We check if there are remaining rows that should be migrated (for example
- # if Sidekiq / Redis fails / is restarted, what could result in not all
- # background migrations being executed correctly.
- #
- # We migrate remaining rows synchronously in a blocking way, to make sure
- # that when this migration is done we are confident that all rows are
- # already migrated.
- #
- Build.where('stage_id IS NULL').each_batch(of: 50) do |batch|
- range = batch.pluck('MIN(id)', 'MAX(id)').first
-
- Gitlab::BackgroundMigration::MigrateBuildStage.new.perform(*range)
+ disable_statement_timeout do
+ ##
+ # We steal from the background migrations queue to catch up with the
+ # scheduled migrations set.
+ #
+ Gitlab::BackgroundMigration.steal('MigrateBuildStage')
+
+ ##
+ # We add temporary index, to make iteration over batches more performant.
+ # Conditional here is to avoid the need of doing that in a separate
+ # migration file to make this operation idempotent.
+ #
+ unless index_exists_by_name?(:ci_builds, TMP_INDEX)
+ add_concurrent_index(:ci_builds, :id, where: 'stage_id IS NULL', name: TMP_INDEX)
+ end
+
+ ##
+ # We check if there are remaining rows that should be migrated (for example
+ # if Sidekiq / Redis fails / is restarted, what could result in not all
+ # background migrations being executed correctly.
+ #
+ # We migrate remaining rows synchronously in a blocking way, to make sure
+ # that when this migration is done we are confident that all rows are
+ # already migrated.
+ #
+ Build.where('stage_id IS NULL').each_batch(of: 50) do |batch|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ Gitlab::BackgroundMigration::MigrateBuildStage.new.perform(*range)
+ end
+
+ ##
+ # We remove temporary index, because it is not required during standard
+ # operations and runtime.
+ #
+ remove_concurrent_index_by_name(:ci_builds, TMP_INDEX)
end
-
- ##
- # We remove temporary index, because it is not required during standard
- # operations and runtime.
- #
- remove_concurrent_index_by_name(:ci_builds, TMP_INDEX)
end
def down
if index_exists_by_name?(:ci_builds, TMP_INDEX)
- remove_concurrent_index_by_name(:ci_builds, TMP_INDEX)
+ disable_statement_timeout do
+ remove_concurrent_index_by_name(:ci_builds, TMP_INDEX)
+ end
end
end
end
diff --git a/db/migrate/20180504195842_project_name_lower_index.rb b/db/migrate/20180504195842_project_name_lower_index.rb
index d6f25d3d4ab..74f3673bb03 100644
--- a/db/migrate/20180504195842_project_name_lower_index.rb
+++ b/db/migrate/20180504195842_project_name_lower_index.rb
@@ -13,20 +13,20 @@ class ProjectNameLowerIndex < ActiveRecord::Migration
def up
return unless Gitlab::Database.postgresql?
- disable_statement_timeout
-
- execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON projects (LOWER(name))"
+ disable_statement_timeout do
+ execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON projects (LOWER(name))"
+ end
end
def down
return unless Gitlab::Database.postgresql?
- disable_statement_timeout
-
- if supports_drop_index_concurrently?
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}"
- else
- execute "DROP INDEX IF EXISTS #{INDEX_NAME}"
+ disable_statement_timeout do
+ if supports_drop_index_concurrently?
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}"
+ else
+ execute "DROP INDEX IF EXISTS #{INDEX_NAME}"
+ end
end
end
end
diff --git a/db/migrate/20180702124358_remove_orphaned_routes.rb b/db/migrate/20180702124358_remove_orphaned_routes.rb
index 6f6e289ba87..4068e479b6c 100644
--- a/db/migrate/20180702124358_remove_orphaned_routes.rb
+++ b/db/migrate/20180702124358_remove_orphaned_routes.rb
@@ -28,16 +28,16 @@ class RemoveOrphanedRoutes < ActiveRecord::Migration
# which is pretty close to our 15 second statement timeout. To ensure a
# smooth deployment procedure we disable the statement timeouts for this
# migration, just in case.
- disable_statement_timeout
-
- # On GitLab.com there are around 4000 orphaned project routes, and around
- # 150 orphaned namespace routes.
- [
- Route.orphaned_project_routes,
- Route.orphaned_namespace_routes
- ].each do |relation|
- relation.each_batch(of: 1_000) do |batch|
- batch.delete_all
+ disable_statement_timeout do
+ # On GitLab.com there are around 4000 orphaned project routes, and around
+ # 150 orphaned namespace routes.
+ [
+ Route.orphaned_project_routes,
+ Route.orphaned_namespace_routes
+ ].each do |relation|
+ relation.each_batch(of: 1_000) do |batch|
+ batch.delete_all
+ end
end
end
end
diff --git a/db/migrate/20180808162000_add_user_show_add_ssh_key_message_to_application_settings.rb b/db/migrate/20180808162000_add_user_show_add_ssh_key_message_to_application_settings.rb
new file mode 100644
index 00000000000..e3019af2cc9
--- /dev/null
+++ b/db/migrate/20180808162000_add_user_show_add_ssh_key_message_to_application_settings.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddUserShowAddSshKeyMessageToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :application_settings, :user_show_add_ssh_key_message, :boolean, default: true, allow_null: false
+ end
+
+ def down
+ remove_column :application_settings, :user_show_add_ssh_key_message
+ end
+end
diff --git a/db/optional_migrations/composite_primary_keys.rb b/db/optional_migrations/composite_primary_keys.rb
index d45705021b0..b330da13d43 100644
--- a/db/optional_migrations/composite_primary_keys.rb
+++ b/db/optional_migrations/composite_primary_keys.rb
@@ -29,18 +29,20 @@ class CompositePrimaryKeysMigration < ActiveRecord::Migration
def up
return unless Gitlab::Database.postgresql?
- disable_statement_timeout
- TABLES.each do |index|
- add_primary_key(index)
+ disable_statement_timeout do
+ TABLES.each do |index|
+ add_primary_key(index)
+ end
end
end
def down
return unless Gitlab::Database.postgresql?
- disable_statement_timeout
- TABLES.each do |index|
- remove_primary_key(index)
+ disable_statement_timeout do
+ TABLES.each do |index|
+ remove_primary_key(index)
+ end
end
end
diff --git a/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb b/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb
index bba37e32c01..845c6f0557f 100644
--- a/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb
+++ b/db/post_migrate/20170502070007_enable_auto_cancel_pending_pipelines_for_all.rb
@@ -8,9 +8,9 @@ class EnableAutoCancelPendingPipelinesForAll < ActiveRecord::Migration
DOWNTIME = false
def up
- disable_statement_timeout
-
- update_column_in_batches(:projects, :auto_cancel_pending_pipelines, 1)
+ disable_statement_timeout do
+ update_column_in_batches(:projects, :auto_cancel_pending_pipelines, 1)
+ end
end
def down
diff --git a/db/post_migrate/20170503004427_update_retried_for_ci_build.rb b/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
index b0b58ab3011..079f0e7511f 100644
--- a/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
+++ b/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
@@ -7,12 +7,12 @@ class UpdateRetriedForCiBuild < ActiveRecord::Migration
disable_ddl_transaction!
def up
- disable_statement_timeout
-
if Gitlab::Database.mysql?
up_mysql
else
- up_postgres
+ disable_statement_timeout do
+ up_postgres
+ end
end
end
diff --git a/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb b/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb
index 81e9d050668..5df3ab71648 100644
--- a/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb
+++ b/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb
@@ -7,20 +7,20 @@ class AddHeadPipelineForEachMergeRequest < ActiveRecord::Migration
disable_ddl_transaction!
def up
- disable_statement_timeout
-
pipelines = Arel::Table.new(:ci_pipelines)
merge_requests = Arel::Table.new(:merge_requests)
- head_id = pipelines
- .project(Arel::Nodes::NamedFunction.new('max', [pipelines[:id]]))
- .from(pipelines)
- .where(pipelines[:ref].eq(merge_requests[:source_branch]))
- .where(pipelines[:project_id].eq(merge_requests[:source_project_id]))
+ disable_statement_timeout do
+ head_id = pipelines
+ .project(Arel::Nodes::NamedFunction.new('max', [pipelines[:id]]))
+ .from(pipelines)
+ .where(pipelines[:ref].eq(merge_requests[:source_branch]))
+ .where(pipelines[:project_id].eq(merge_requests[:source_project_id]))
- sub_query = Arel::Nodes::SqlLiteral.new(Arel::Nodes::Grouping.new(head_id).to_sql)
+ sub_query = Arel::Nodes::SqlLiteral.new(Arel::Nodes::Grouping.new(head_id).to_sql)
- update_column_in_batches(:merge_requests, :head_pipeline_id, sub_query)
+ update_column_in_batches(:merge_requests, :head_pipeline_id, sub_query)
+ end
end
def down
diff --git a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb
index 2125cc046e5..c996ddbec84 100644
--- a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb
+++ b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb
@@ -87,16 +87,16 @@ class RenameAllReservedPathsAgain < ActiveRecord::Migration
].freeze
def up
- disable_statement_timeout
-
- TOP_LEVEL_ROUTES.each { |route| rename_root_paths(route) }
- PROJECT_WILDCARD_ROUTES.each { |route| rename_wildcard_paths(route) }
- GROUP_ROUTES.each { |route| rename_child_paths(route) }
+ disable_statement_timeout do
+ TOP_LEVEL_ROUTES.each { |route| rename_root_paths(route) }
+ PROJECT_WILDCARD_ROUTES.each { |route| rename_wildcard_paths(route) }
+ GROUP_ROUTES.each { |route| rename_child_paths(route) }
+ end
end
def down
- disable_statement_timeout
-
- revert_renames
+ disable_statement_timeout do
+ revert_renames
+ end
end
end
diff --git a/db/post_migrate/20170526185842_migrate_pipeline_stages.rb b/db/post_migrate/20170526185842_migrate_pipeline_stages.rb
index afd4db183c2..736aff77f02 100644
--- a/db/post_migrate/20170526185842_migrate_pipeline_stages.rb
+++ b/db/post_migrate/20170526185842_migrate_pipeline_stages.rb
@@ -6,17 +6,17 @@ class MigratePipelineStages < ActiveRecord::Migration
disable_ddl_transaction!
def up
- disable_statement_timeout
-
- execute <<-SQL.strip_heredoc
- INSERT INTO ci_stages (project_id, pipeline_id, name)
- SELECT project_id, commit_id, stage FROM ci_builds
- WHERE stage IS NOT NULL
- AND stage_id IS NULL
- AND EXISTS (SELECT 1 FROM projects WHERE projects.id = ci_builds.project_id)
- AND EXISTS (SELECT 1 FROM ci_pipelines WHERE ci_pipelines.id = ci_builds.commit_id)
- GROUP BY project_id, commit_id, stage
- ORDER BY MAX(stage_idx)
- SQL
+ disable_statement_timeout do
+ execute <<-SQL.strip_heredoc
+ INSERT INTO ci_stages (project_id, pipeline_id, name)
+ SELECT project_id, commit_id, stage FROM ci_builds
+ WHERE stage IS NOT NULL
+ AND stage_id IS NULL
+ AND EXISTS (SELECT 1 FROM projects WHERE projects.id = ci_builds.project_id)
+ AND EXISTS (SELECT 1 FROM ci_pipelines WHERE ci_pipelines.id = ci_builds.commit_id)
+ GROUP BY project_id, commit_id, stage
+ ORDER BY MAX(stage_idx)
+ SQL
+ end
end
end
diff --git a/db/post_migrate/20170526190000_migrate_build_stage_reference_again.rb b/db/post_migrate/20170526190000_migrate_build_stage_reference_again.rb
index 31a73bb3b27..a7bfba0ab2b 100644
--- a/db/post_migrate/20170526190000_migrate_build_stage_reference_again.rb
+++ b/db/post_migrate/20170526190000_migrate_build_stage_reference_again.rb
@@ -7,22 +7,22 @@ class MigrateBuildStageReferenceAgain < ActiveRecord::Migration
disable_ddl_transaction!
def up
- disable_statement_timeout
-
stage_id = Arel.sql <<-SQL.strip_heredoc
(SELECT id FROM ci_stages
WHERE ci_stages.pipeline_id = ci_builds.commit_id
AND ci_stages.name = ci_builds.stage)
SQL
- update_column_in_batches(:ci_builds, :stage_id, stage_id) do |table, query|
- query.where(table[:stage_id].eq(nil))
+ disable_statement_timeout do
+ update_column_in_batches(:ci_builds, :stage_id, stage_id) do |table, query|
+ query.where(table[:stage_id].eq(nil))
+ end
end
end
def down
- disable_statement_timeout
-
- update_column_in_batches(:ci_builds, :stage_id, nil)
+ disable_statement_timeout do
+ update_column_in_batches(:ci_builds, :stage_id, nil)
+ end
end
end
diff --git a/db/post_migrate/20170711145558_migrate_stages_statuses.rb b/db/post_migrate/20170711145558_migrate_stages_statuses.rb
index 65755c0e824..265f7317b9b 100644
--- a/db/post_migrate/20170711145558_migrate_stages_statuses.rb
+++ b/db/post_migrate/20170711145558_migrate_stages_statuses.rb
@@ -26,9 +26,9 @@ class MigrateStagesStatuses < ActiveRecord::Migration
end
def down
- disable_statement_timeout
-
- # rubocop:disable Migration/UpdateLargeTable
- update_column_in_batches(:ci_stages, :status, nil)
+ disable_statement_timeout do
+ # rubocop:disable Migration/UpdateLargeTable
+ update_column_in_batches(:ci_stages, :status, nil)
+ end
end
end
diff --git a/db/post_migrate/20171207150343_remove_soft_removed_objects.rb b/db/post_migrate/20171207150343_remove_soft_removed_objects.rb
index 3e2dedfdd6a..3109b6dbf8e 100644
--- a/db/post_migrate/20171207150343_remove_soft_removed_objects.rb
+++ b/db/post_migrate/20171207150343_remove_soft_removed_objects.rb
@@ -78,12 +78,12 @@ class RemoveSoftRemovedObjects < ActiveRecord::Migration
MODELS = [Issue, MergeRequest, CiPipelineSchedule, CiTrigger].freeze
def up
- disable_statement_timeout
-
- remove_personal_routes
- remove_personal_namespaces
- remove_group_namespaces
- remove_simple_soft_removed_rows
+ disable_statement_timeout do
+ remove_personal_routes
+ remove_personal_namespaces
+ remove_group_namespaces
+ remove_simple_soft_removed_rows
+ end
end
def down
diff --git a/db/post_migrate/20180119121225_remove_redundant_pipeline_stages.rb b/db/post_migrate/20180119121225_remove_redundant_pipeline_stages.rb
index 61ea85eb2a7..269f1287f91 100644
--- a/db/post_migrate/20180119121225_remove_redundant_pipeline_stages.rb
+++ b/db/post_migrate/20180119121225_remove_redundant_pipeline_stages.rb
@@ -38,29 +38,29 @@ class RemoveRedundantPipelineStages < ActiveRecord::Migration
end
def remove_redundant_pipeline_stages!
- disable_statement_timeout
-
- redundant_stages_ids = <<~SQL
- SELECT id FROM ci_stages WHERE (pipeline_id, name) IN (
- SELECT pipeline_id, name FROM ci_stages
- GROUP BY pipeline_id, name HAVING COUNT(*) > 1
- )
- SQL
-
- execute <<~SQL
- UPDATE ci_builds SET stage_id = NULL WHERE stage_id IN (#{redundant_stages_ids})
- SQL
-
- if Gitlab::Database.postgresql?
- execute <<~SQL
- DELETE FROM ci_stages WHERE id IN (#{redundant_stages_ids})
+ disable_statement_timeout do
+ redundant_stages_ids = <<~SQL
+ SELECT id FROM ci_stages WHERE (pipeline_id, name) IN (
+ SELECT pipeline_id, name FROM ci_stages
+ GROUP BY pipeline_id, name HAVING COUNT(*) > 1
+ )
SQL
- else # We can't modify a table we are selecting from on MySQL
+
execute <<~SQL
- DELETE a FROM ci_stages AS a, ci_stages AS b
- WHERE a.pipeline_id = b.pipeline_id AND a.name = b.name
- AND a.id <> b.id
+ UPDATE ci_builds SET stage_id = NULL WHERE stage_id IN (#{redundant_stages_ids})
SQL
+
+ if Gitlab::Database.postgresql?
+ execute <<~SQL
+ DELETE FROM ci_stages WHERE id IN (#{redundant_stages_ids})
+ SQL
+ else # We can't modify a table we are selecting from on MySQL
+ execute <<~SQL
+ DELETE a FROM ci_stages AS a, ci_stages AS b
+ WHERE a.pipeline_id = b.pipeline_id AND a.name = b.name
+ AND a.id <> b.id
+ SQL
+ end
end
end
end
diff --git a/db/post_migrate/20180305100050_remove_permanent_from_redirect_routes.rb b/db/post_migrate/20180305100050_remove_permanent_from_redirect_routes.rb
index db5165dbe70..aa19732ca1c 100644
--- a/db/post_migrate/20180305100050_remove_permanent_from_redirect_routes.rb
+++ b/db/post_migrate/20180305100050_remove_permanent_from_redirect_routes.rb
@@ -15,10 +15,10 @@ class RemovePermanentFromRedirectRoutes < ActiveRecord::Migration
# ReworkRedirectRoutesIndexes:
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16211
if Gitlab::Database.postgresql?
- disable_statement_timeout
-
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_PERM};"
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_TEMP};"
+ disable_statement_timeout do
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_PERM};"
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME_TEMP};"
+ end
end
remove_column(:redirect_routes, :permanent)
@@ -28,10 +28,10 @@ class RemovePermanentFromRedirectRoutes < ActiveRecord::Migration
add_column(:redirect_routes, :permanent, :boolean)
if Gitlab::Database.postgresql?
- disable_statement_timeout
-
- execute("CREATE INDEX CONCURRENTLY #{INDEX_NAME_PERM} ON redirect_routes (lower(path) varchar_pattern_ops) where (permanent);")
- execute("CREATE INDEX CONCURRENTLY #{INDEX_NAME_TEMP} ON redirect_routes (lower(path) varchar_pattern_ops) where (not permanent or permanent is null) ;")
+ disable_statement_timeout do
+ execute("CREATE INDEX CONCURRENTLY #{INDEX_NAME_PERM} ON redirect_routes (lower(path) varchar_pattern_ops) where (permanent);")
+ execute("CREATE INDEX CONCURRENTLY #{INDEX_NAME_TEMP} ON redirect_routes (lower(path) varchar_pattern_ops) where (not permanent or permanent is null) ;")
+ end
end
end
end
diff --git a/db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb b/db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb
index d6fb4f06695..ca9212fae27 100644
--- a/db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb
+++ b/db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb
@@ -20,10 +20,10 @@ class AddPathIndexToRedirectRoutes < ActiveRecord::Migration
def up
return unless Gitlab::Database.postgresql?
- disable_statement_timeout
-
- unless index_exists_by_name?(:redirect_routes, INDEX_NAME)
- execute("CREATE UNIQUE INDEX CONCURRENTLY #{INDEX_NAME} ON redirect_routes (lower(path) varchar_pattern_ops);")
+ disable_statement_timeout do
+ unless index_exists_by_name?(:redirect_routes, INDEX_NAME)
+ execute("CREATE UNIQUE INDEX CONCURRENTLY #{INDEX_NAME} ON redirect_routes (lower(path) varchar_pattern_ops);")
+ end
end
end
diff --git a/db/post_migrate/20180405101928_reschedule_builds_stages_migration.rb b/db/post_migrate/20180405101928_reschedule_builds_stages_migration.rb
index e19387bce1e..c32123454f9 100644
--- a/db/post_migrate/20180405101928_reschedule_builds_stages_migration.rb
+++ b/db/post_migrate/20180405101928_reschedule_builds_stages_migration.rb
@@ -17,13 +17,13 @@ class RescheduleBuildsStagesMigration < ActiveRecord::Migration
end
def up
- disable_statement_timeout
-
- Build.where('stage_id IS NULL').tap do |relation|
- queue_background_migration_jobs_by_range_at_intervals(relation,
- MIGRATION,
- 5.minutes,
- batch_size: BATCH_SIZE)
+ disable_statement_timeout do
+ Build.where('stage_id IS NULL').tap do |relation|
+ queue_background_migration_jobs_by_range_at_intervals(relation,
+ MIGRATION,
+ 5.minutes,
+ batch_size: BATCH_SIZE)
+ end
end
end
diff --git a/db/post_migrate/20180420080616_schedule_stages_index_migration.rb b/db/post_migrate/20180420080616_schedule_stages_index_migration.rb
index 1d0daad002f..eb82f098639 100644
--- a/db/post_migrate/20180420080616_schedule_stages_index_migration.rb
+++ b/db/post_migrate/20180420080616_schedule_stages_index_migration.rb
@@ -13,13 +13,13 @@ class ScheduleStagesIndexMigration < ActiveRecord::Migration
end
def up
- disable_statement_timeout
-
- Stage.all.tap do |relation|
- queue_background_migration_jobs_by_range_at_intervals(relation,
- MIGRATION,
- 5.minutes,
- batch_size: BATCH_SIZE)
+ disable_statement_timeout do
+ Stage.all.tap do |relation|
+ queue_background_migration_jobs_by_range_at_intervals(relation,
+ MIGRATION,
+ 5.minutes,
+ batch_size: BATCH_SIZE)
+ end
end
end
diff --git a/db/post_migrate/20180604123514_cleanup_stages_position_migration.rb b/db/post_migrate/20180604123514_cleanup_stages_position_migration.rb
index 73c23dffca0..5418f442e79 100644
--- a/db/post_migrate/20180604123514_cleanup_stages_position_migration.rb
+++ b/db/post_migrate/20180604123514_cleanup_stages_position_migration.rb
@@ -12,32 +12,34 @@ class CleanupStagesPositionMigration < ActiveRecord::Migration
end
def up
- disable_statement_timeout
+ disable_statement_timeout do
+ Gitlab::BackgroundMigration.steal('MigrateStageIndex')
- Gitlab::BackgroundMigration.steal('MigrateStageIndex')
-
- unless index_exists_by_name?(:ci_stages, TMP_INDEX_NAME)
- add_concurrent_index(:ci_stages, :id, where: 'position IS NULL', name: TMP_INDEX_NAME)
- end
+ unless index_exists_by_name?(:ci_stages, TMP_INDEX_NAME)
+ add_concurrent_index(:ci_stages, :id, where: 'position IS NULL', name: TMP_INDEX_NAME)
+ end
- migratable = <<~SQL
- position IS NULL AND EXISTS (
- SELECT 1 FROM ci_builds WHERE stage_id = ci_stages.id AND stage_idx IS NOT NULL
- )
- SQL
+ migratable = <<~SQL
+ position IS NULL AND EXISTS (
+ SELECT 1 FROM ci_builds WHERE stage_id = ci_stages.id AND stage_idx IS NOT NULL
+ )
+ SQL
- Stages.where(migratable).each_batch(of: 1000) do |batch|
- batch.pluck(:id).each do |stage|
- Gitlab::BackgroundMigration::MigrateStageIndex.new.perform(stage, stage)
+ Stages.where(migratable).each_batch(of: 1000) do |batch|
+ batch.pluck(:id).each do |stage|
+ Gitlab::BackgroundMigration::MigrateStageIndex.new.perform(stage, stage)
+ end
end
- end
- remove_concurrent_index_by_name(:ci_stages, TMP_INDEX_NAME)
+ remove_concurrent_index_by_name(:ci_stages, TMP_INDEX_NAME)
+ end
end
def down
if index_exists_by_name?(:ci_stages, TMP_INDEX_NAME)
- remove_concurrent_index_by_name(:ci_stages, TMP_INDEX_NAME)
+ disable_statement_timeout do
+ remove_concurrent_index_by_name(:ci_stages, TMP_INDEX_NAME)
+ end
end
end
end
diff --git a/db/schema.rb b/db/schema.rb
index f1d8f4df3b7..1288a98745c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180807153545) do
+ActiveRecord::Schema.define(version: 20180808162000) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -170,6 +170,7 @@ ActiveRecord::Schema.define(version: 20180807153545) do
t.boolean "hide_third_party_offers", default: false, null: false
t.boolean "instance_statistics_visibility_private", default: false, null: false
t.boolean "web_ide_clientside_preview_enabled", default: false, null: false
+ t.boolean "user_show_add_ssh_key_message", default: true, null: false
end
create_table "audit_events", force: :cascade do |t|
diff --git a/doc/README.md b/doc/README.md
index a814c787f94..4248f62c08c 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -133,6 +133,7 @@ scales to run your tests faster.
- [GitLab CI/CD](ci/README.md): Explore the features and capabilities of Continuous Integration, Continuous Delivery, and Continuous Deployment with GitLab.
- [Review Apps](ci/review_apps/index.md): Preview changes to your app right from a merge request.
- [Pipeline Graphs](ci/pipelines.md#pipeline-graphs)
+- [JUnit test reports](ci/junit_test_reports.md)
### Package
diff --git a/doc/administration/operations/ssh_certificates.md b/doc/administration/operations/ssh_certificates.md
index 8968afba01b..9edccd25ced 100644
--- a/doc/administration/operations/ssh_certificates.md
+++ b/doc/administration/operations/ssh_certificates.md
@@ -163,3 +163,20 @@ Such a restriction can currently be hacked in by e.g. providing a
custom `AuthorizedKeysCommand` which checks if the discovered key-ID
returned from `gitlab-shell-authorized-keys-check` is a deploy key or
not (all non-deploy keys should be refused).
+
+## Disabling the global warning about users lacking SSH keys
+
+By default GitLab will show a "You won't be able to pull or push
+project code via SSH" warning to users who have not uploaded an SSH
+key to their profile.
+
+This is counterproductive when using SSH certificates, since users
+aren't expected to upload their own keys.
+
+To disable this warning globally, go to "Application settings ->
+Account and limit settings" and disable the "Show user add SSH key
+message" setting.
+
+This setting was added specifically for use with SSH certificates, but
+can be turned off without using them if you'd like to hide the warning
+for some other reason.
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 246de50323e..5f006f4f012 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -144,7 +144,7 @@ Example response:
## List board lists
Get a list of the board's lists.
-Does not include `backlog` and `closed` lists
+Does not include `open` and `closed` lists
```
GET /projects/:id/boards/:board_id/lists
diff --git a/doc/api/events.md b/doc/api/events.md
index f4d26c4de1c..1b6c4d437dd 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -255,7 +255,7 @@ Example response:
Get a list of visible events for a particular project.
```
-GET /:project_id/events
+GET /projects/:project_id/events
```
Parameters:
diff --git a/doc/api/group_boards.md b/doc/api/group_boards.md
index 45a8544d6b1..373904e50c4 100644
--- a/doc/api/group_boards.md
+++ b/doc/api/group_boards.md
@@ -119,7 +119,7 @@ Example response:
## List board lists
Get a list of the board's lists.
-Does not include `backlog` and `closed` lists
+Does not include `open` and `closed` lists
```
GET /groups/:id/boards/:board_id/lists
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 9a950097675..4bf65a8fafd 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -33,7 +33,6 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.727Z",
- "artifacts_file": null,
"finished_at": "2015-12-24T17:54:24.921Z",
"artifacts_expire_at": "2016-01-23T17:54:24.921Z",
"id": 6,
@@ -45,6 +44,7 @@ Example of response
"status": "pending"
},
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"started_at": "2015-12-24T17:54:24.729Z",
@@ -82,6 +82,12 @@ Example of response
"filename": "artifacts.zip",
"size": 1000
},
+ "artifacts": [
+ {"file_type": "archive", "size": 1000, "filename": "artifacts.zip", "file_format": "zip"},
+ {"file_type": "metadata", "size": 186, "filename": "metadata.gz", "file_format": "gzip"},
+ {"file_type": "trace", "size": 1500, "filename": "job.log", "file_format": "raw"},
+ {"file_type": "junit", "size": 750, "filename": "junit.xml.gz", "file_format": "gzip"}
+ ],
"finished_at": "2015-12-24T17:54:27.895Z",
"artifacts_expire_at": "2016-01-23T17:54:27.895Z",
"id": 7,
@@ -93,6 +99,7 @@ Example of response
"status": "pending"
},
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"started_at": "2015-12-24T17:54:27.722Z",
@@ -151,7 +158,6 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.727Z",
- "artifacts_file": null,
"finished_at": "2015-12-24T17:54:24.921Z",
"artifacts_expire_at": "2016-01-23T17:54:24.921Z",
"id": 6,
@@ -163,6 +169,7 @@ Example of response
"status": "pending"
},
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"started_at": "2015-12-24T17:54:24.729Z",
@@ -200,6 +207,12 @@ Example of response
"filename": "artifacts.zip",
"size": 1000
},
+ "artifacts": [
+ {"file_type": "archive", "size": 1000, "filename": "artifacts.zip", "file_format": "zip"},
+ {"file_type": "metadata", "size": 186, "filename": "metadata.gz", "file_format": "gzip"},
+ {"file_type": "trace", "size": 1500, "filename": "job.log", "file_format": "raw"},
+ {"file_type": "junit", "size": 750, "filename": "junit.xml.gz", "file_format": "gzip"}
+ ],
"finished_at": "2015-12-24T17:54:27.895Z",
"artifacts_expire_at": "2016-01-23T17:54:27.895Z",
"id": 7,
@@ -211,6 +224,7 @@ Example of response
"status": "pending"
},
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"started_at": "2015-12-24T17:54:27.722Z",
@@ -267,7 +281,6 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.880Z",
- "artifacts_file": null,
"finished_at": "2015-12-24T17:54:31.198Z",
"artifacts_expire_at": "2016-01-23T17:54:31.198Z",
"id": 8,
@@ -279,6 +292,7 @@ Example of response
"status": "pending"
},
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"started_at": "2015-12-24T17:54:30.733Z",
@@ -458,11 +472,11 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
- "artifacts_file": null,
"finished_at": "2016-01-11T10:14:09.526Z",
"id": 42,
"name": "rubocop",
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"started_at": null,
@@ -505,11 +519,11 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
- "artifacts_file": null,
"finished_at": null,
"id": 42,
"name": "rubocop",
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"started_at": null,
@@ -559,6 +573,7 @@ Example of response
"id": 42,
"name": "rubocop",
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"created_at": "2016-01-11T10:13:33.506Z",
@@ -610,6 +625,7 @@ Example response:
"id": 42,
"name": "rubocop",
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"created_at": "2016-01-11T10:13:33.506Z",
@@ -654,11 +670,11 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
- "artifacts_file": null,
"finished_at": null,
"id": 42,
"name": "rubocop",
"ref": "master",
+ "artifacts": [],
"runner": null,
"stage": "test",
"started_at": null,
diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md
index 682b90361bd..165b9a11c7a 100644
--- a/doc/api/notification_settings.md
+++ b/doc/api/notification_settings.md
@@ -15,7 +15,7 @@ mention
custom
```
-If the `custom` level is used, specific email events can be controlled. Notification email events are defined in the `NotificationSetting::EMAIL_EVENTS` model variable. Currently, these events are recognized:
+If the `custom` level is used, specific email events can be controlled. Available events are returned by `NotificationSetting.email_events`. Currently, these events are recognized:
```
new_note
diff --git a/doc/api/projects.md b/doc/api/projects.md
index f360b49c293..bda4164ee92 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -258,8 +258,7 @@ GET /projects?custom_attributes[key]=value&custom_attributes[other_key]=other_va
## List user projects
-Get a list of visible projects for the given user. When accessed without
-authentication, only public projects are returned.
+Get a list of visible projects owned by the given user. When accessed without authentication, only public projects are returned.
```
GET /users/:user_id/projects
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index cb816bbd712..a4fdeca162e 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -204,3 +204,39 @@ Response:
"deletions": 244
}]
```
+
+## Merge Base
+
+Get the common ancestor for 2 refs (commit SHAs, branch names or tags).
+
+```
+GET /projects/:id/repository/merge_base
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `refs` | array | yes | The refs to find the common ancestor of, for now only 2 refs are supported |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/merge_base?refs[]=304d257dcb821665ab5110318fc58a007bd104ed&refs[]=0031876facac3f2b2702a0e53a26e89939a42209"
+```
+
+Example response:
+
+```json
+{
+ "id": "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863",
+ "short_id": "1a0b36b3",
+ "title": "Initial commit",
+ "created_at": "2014-02-27T08:03:18.000Z",
+ "parent_ids": [],
+ "message": "Initial commit\n",
+ "author_name": "Dmitriy Zaporozhets",
+ "author_email": "dmitriy.zaporozhets@gmail.com",
+ "authored_date": "2014-02-27T08:03:18.000Z",
+ "committer_name": "Dmitriy Zaporozhets",
+ "committer_email": "dmitriy.zaporozhets@gmail.com",
+ "committed_date": "2014-02-27T08:03:18.000Z"
+}
+```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 68fc56b1fa3..b480d62e16a 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -56,7 +56,8 @@ Example response:
"enforce_terms": true,
"terms": "Hello world!",
"performance_bar_allowed_group_id": 42,
- "instance_statistics_visibility_private": false
+ "instance_statistics_visibility_private": false,
+ "user_show_add_ssh_key_message": true
}
```
@@ -161,6 +162,8 @@ PUT /application/settings
| `enforce_terms` | boolean | no | Enforce application ToS to all users |
| `terms` | text | yes (if `enforce_terms` is true) | Markdown content for the ToS |
| `instance_statistics_visibility_private` | boolean | no | When set to `true` Instance statistics will only be available to admins |
+| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push
++project code via SSH" warning shown to users with no uploaded SSH key |
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal
@@ -206,6 +209,7 @@ Example response:
"enforce_terms": true,
"terms": "Hello world!",
"performance_bar_allowed_group_id": 42,
- "instance_statistics_visibility_private": false
+ "instance_statistics_visibility_private": false,
+ "user_show_add_ssh_key_message": true
}
```
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index dd424470b67..7b8db6cfa8f 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -34,6 +34,7 @@ Example response:
"push_events":true,
"tag_push_events":false,
"merge_requests_events": true,
+ "repository_update_events": true,
"enable_ssl_verification":true
}
]
@@ -56,6 +57,7 @@ POST /hooks
| `push_events` | boolean | no | When true, the hook will fire on push events |
| `tag_push_events` | boolean | no | When true, the hook will fire on new tags being pushed |
| `merge_requests_events` | boolean | no | Trigger hook on merge requests events |
+| `repository_update_events` | boolean | no | Trigger hook on repository update events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
Example request:
@@ -75,6 +77,7 @@ Example response:
"push_events":true,
"tag_push_events":false,
"merge_requests_events": true,
+ "repository_update_events": true,
"enable_ssl_verification":true
}
]
@@ -127,4 +130,4 @@ Example request:
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/hooks/2
-```
+``` \ No newline at end of file
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 811f4d1f07a..8eb96ae10b2 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -43,6 +43,10 @@ There's also a collection of repositories with [example projects](https://gitlab
- [Using `dpl` as deployment tool](deployment/README.md)
- [The `.gitlab-ci.yml` file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+## Test Reports
+
+[Collect test reports in Verify stage](../junit_test_reports.md).
+
## Code Quality analysis
**(Starter)** [Analyze your project's Code Quality](code_quality.md).
diff --git a/doc/ci/img/junit_test_report.png b/doc/ci/img/junit_test_report.png
new file mode 100644
index 00000000000..ad098eb457f
--- /dev/null
+++ b/doc/ci/img/junit_test_report.png
Binary files differ
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
new file mode 100644
index 00000000000..5ae8ecaafa6
--- /dev/null
+++ b/doc/ci/junit_test_reports.md
@@ -0,0 +1,102 @@
+# JUnit test reports
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45318) in GitLab 11.2.
+Requires GitLab Runner 11.2 and above.
+
+## Overview
+
+It is very common that a [CI/CD pipeline](pipelines.md) contains a
+test job that will verify your code.
+If the tests fail, the pipeline fails and users get notified. The person that
+works on the merge request will have to check the job logs and see where the
+tests failed so that they can fix them.
+
+You can configure your job to use JUnit test reports, and GitLab will display a
+report on the merge request so that it's easier and faster to identify the
+failure without having to check the entire log.
+
+## Use cases
+
+Consider the following workflow:
+
+1. Your `master` branch is rock solid, your project is using GitLab CI/CD and
+ your pipelines indicate that there isn't anything broken.
+1. Someone from you team submits a merge request, a test fails and the pipeline
+ gets the known red icon. To investigate more, you have to go through the job
+ logs to figure out the cause of the failed test, which usually contain
+ thousands of lines.
+1. You configure the JUnit test reports and immediately GitLab collects and
+ exposes them in the merge request. No more searching in the job logs.
+1. Your development and debugging workflow becomes easier, faster and efficient.
+
+## How it works
+
+First, GitLab Runner uploads all JUnit XML files as artifacts to GitLab. Then,
+when you visit a merge request, GitLab starts comparing the head and base branch's
+JUnit test reports, where:
+
+- The base branch is the target branch (usually `master`).
+- The head branch is the source branch (the latest pipeline in each merge request).
+
+The reports panel has a summary showing how many tests failed and how many were fixed.
+If no comparison can be done because data for the base branch is not available,
+the panel will just show the list of failed tests for head.
+
+There are three types of results:
+
+1. **Newly failed tests:** Test cases which passed on base branch and failed on head branch
+1. **Existing failures:** Test cases which failed on base branch and failed on head branch
+1. **Resolved failures:** Test cases which failed on base branch and passed on head branch
+
+Each entry in the panel will show the test name and its type from the list
+above. Clicking on the test name will open a modal window with details of its
+execution time and the error output.
+
+![Test Reports Widget](img/junit_test_report.png)
+
+## How to set it up
+
+NOTE: **Note:**
+For a list of supported languages on JUnit tests, check the
+[Wikipedia article](https://en.wikipedia.org/wiki/JUnit#Ports).
+
+To enable the JUnit reports in merge requests, you need to add
+[`artifacts:reports:junit`](yaml/README.md#artifacts-reports-junit)
+in `.gitlab-ci.yml`, and specify the path(s) of the generated test reports.
+
+In the following examples, the job in the `test` stage runs and GitLab
+collects the JUnit test report from each job. After each job is executed, the
+XML reports are stored in GitLab as artifacts and their results are shown in the
+merge request widget.
+
+### Ruby example
+
+Use the following job in `.gitlab-ci.yml`:
+
+```yaml
+## Use https://github.com/sj26/rspec_junit_formatter to generate a JUnit report with rspec
+ruby:
+ stage: test
+ script:
+ - bundle install
+ - rspec spec/lib/ --format RspecJunitFormatter --out rspec.xml
+ artifacts:
+ reports:
+ junit: rspec.xml
+```
+
+### Go example
+
+Use the following job in `.gitlab-ci.yml`:
+
+```yaml
+## Use https://github.com/jstemmer/go-junit-report to generate a JUnit report with go
+golang:
+ stage: test
+ script:
+ - go get -u github.com/jstemmer/go-junit-report
+ - go test -v 2>&1 | go-junit-report > report.xml
+ artifacts:
+ reports:
+ junit: report.xml
+```
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 95d705d3a3d..ef740ab1c5e 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1092,6 +1092,52 @@ job:
expire_in: 1 week
```
+### `artifacts:reports`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20390) in
+GitLab 11.2. Requires GitLab Runner 11.2 and above.
+
+The `reports` keyword is used for collecting test reports from jobs and
+exposing them in GitLab's UI (merge requests, pipeline views). Read how to use
+this with [JUnit reports](#artifacts-reports-junit).
+
+NOTE: **Note:**
+The test reports are collected regardless of the job results (success or failure).
+You can use [`artifacts:expire_in`](#artifacts-expire_in) to set up an expiration
+date for their artifacts.
+
+#### `artifacts:reports:junit`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20390) in
+GitLab 11.2. Requires GitLab Runner 11.2 and above.
+
+The `junit` report collects [JUnit XML files](https://www.ibm.com/support/knowledgecenter/en/SSQ2R2_14.1.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html)
+as artifacts. Although JUnit was originally developed in Java, there are many
+[third party ports](https://en.wikipedia.org/wiki/JUnit#Ports) for other
+languages like Javascript, Python, Ruby, etc.
+
+Below is an example of collecting a JUnit XML file from Ruby's RSpec test tool:
+
+```yaml
+rspec:
+ stage: test
+ script:
+ - bundle install
+ - rspec --format RspecJunitFormatter --out rspec.xml
+ artifacts:
+ reports:
+ junit: rspec.xml
+```
+
+The collected JUnit reports will be uploaded to GitLab as an artifact and will
+be automatically [shown in merge requests](../junit_test_reports.md).
+
+NOTE: **Note:**
+In case the JUnit tool you use exports to multiple XML files, you can specify
+multiple test report paths within a single job
+(`junit: [rspec-1.xml, rspec-2.xml, rspec-3.xml]`) and they will be automatically
+concatenated into a single file.
+
## `dependencies`
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
diff --git a/doc/development/contributing/design.md b/doc/development/contributing/design.md
index d4cce79b067..57ae318e821 100644
--- a/doc/development/contributing/design.md
+++ b/doc/development/contributing/design.md
@@ -18,23 +18,16 @@ To better understand the priority by which UX tackles issues, see the [UX sectio
Once an issue has been worked on and is ready for development, a UXer removes the ~"UX" label and applies the ~"UX ready" label to that issue.
-The UX team has a special type label called ~"design artifact". This label indicates that the final output
-for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone.
-Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is
-needed until the solution has been decided.
+There is a special type label called ~"product discovery". It represents a discovery issue intended for UX, PM, FE, and BE to discuss the problem and potential solutions. The final output for this issue could be a doc of requirements, a design artifact, or even a prototype. The solution will be developed in a subsequent milestone.
-~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
+~"product discovery" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
-To prevent the misunderstanding that a feature will be be delivered in the
-assigned milestone, when only UX design is planned for that milestone, the
-Product Manager should create a separate issue for the ~"design artifact",
-assign the ~UX, ~"design artifact" and ~"Deliverable" labels, add a milestone
-and use a title that makes it clear that the scheduled issue is design only
-(e.g. `Design exploration for XYZ`).
+The initial issue should be about the problem we are solving. If a separate [product discovery issue](#product-discovery-issues) is needed for additional research and design work, it will be created by a PM or UX person. Assign the ~UX, ~"product discovery" and ~"Deliverable" labels, add a milestone and use a title that makes it clear that the scheduled issue is product discovery
+(e.g. `Product discovery for XYZ`).
-When the ~"design artifact" issue has been completed, the UXer removes the ~UX
+When the ~"product discovery" issue has been completed, the UXer removes the ~UX
label, adds the ~"UX ready" label and closes the issue. This indicates the
-design artifact is complete. The UXer will also copy the designs to related
+UX work for the issue is complete. The UXer will also copy any designs to related
issues for implementation in an upcoming milestone.
## Style guides
diff --git a/doc/development/diffs.md b/doc/development/diffs.md
index 55fc16e0b33..d06339480b1 100644
--- a/doc/development/diffs.md
+++ b/doc/development/diffs.md
@@ -1,6 +1,6 @@
-# Working with Merge Request diffs
+# Working with diffs
-Currently we rely on different sources to present merge request diffs, these include:
+Currently we rely on different sources to present diffs, these include:
- Rugged gem
- Gitaly service
@@ -11,6 +11,8 @@ We're constantly moving Rugged calls to Gitaly and the progress can be followed
## Architecture overview
+### Merge request diffs
+
When refreshing a Merge Request (pushing to a source branch, force-pushing to target branch, or if the target branch now contains any commits from the MR)
we fetch the comparison information using `Gitlab::Git::Compare`, which fetches `base` and `head` data using Gitaly and diff between them through
`Gitlab::Git::Diff.between` (which uses _Gitaly_ if it's enabled, otherwise _Rugged_).
@@ -32,6 +34,17 @@ In order to present diffs information on the Merge Request diffs page, we:
3. If the diff file is cacheable (text-based), it's cached on Redis
using `Gitlab::Diff::FileCollection::MergeRequestDiff`
+### Note diffs
+
+When commenting on a diff (any comparison), we persist a truncated diff version
+on `NoteDiffFile` (which is associated with the actual `DiffNote`). So instead
+of hitting the repository every time we need the diff of the file, we:
+
+1. Check whether we have the `NoteDiffFile#diff` persisted and use it
+2. Otherwise, if it's a current MR revision, use the persisted
+`MergeRequestDiffFile#diff`
+3. In the last scenario, go the the repository and fetch the diff
+
## Diff limits
As explained above, we limit single diff files and the size of the whole diff. There are scenarios where we collapse the diff file,
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 5d1f657015c..09ea8c05be6 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -20,7 +20,40 @@ dynamic (querying the DB etc.).
Once defined in `lib/feature.rb`, you will be able to activate a
feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
+For GitLab.com, team members have access to feature flags through chatops. Only
+percentage gates are supported at this time. Setting a feature to be used 50% of
+the time, you should execute `/chatops run feature set my_feature_flag 50`.
+
## Feature flags for user applications
GitLab does not yet support the use of feature flags in deployed user applications.
-You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779). \ No newline at end of file
+You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779).
+
+## Developing with feature flags
+
+In general, it's better to have a group- or user-based gate, and you should prefer
+it over the use of percentage gates. This would make debugging easier, as you
+filter for example logs and errors based on actors too. Futhermore, this allows
+for enabling for the `gitlab-org` group first, while the rest of the users
+aren't impacted.
+
+```ruby
+# Good
+Feature.enabled?(:feature_flag, project)
+
+# Avoid, if possible
+Feature.enabled?(:feature_flag)
+```
+
+To use feature gates based on actors, the model needs to respond to
+`flipper_id`. For example, to enable for the Foo model:
+
+```ruby
+class Foo < ActiveRecord::Base
+ include FeatureGate
+end
+```
+
+Features that are developed and are intended to be merged behind a feature flag
+should not include a changelog entry. The entry should be added in the merge
+request removing the feature flags.
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index a211effdfa7..6f31e5b82e5 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -182,6 +182,34 @@ class MyMigration < ActiveRecord::Migration
end
```
+## Adding foreign-key constraints
+
+When adding a foreign-key constraint to either an existing or new
+column remember to also add a index on the column.
+
+This is _required_ if the foreign-key constraint specifies
+`ON DELETE CASCADE` or `ON DELETE SET NULL` behavior. On a cascading
+delete, the [corresponding record needs to be retrieved using an
+index](https://www.cybertec-postgresql.com/en/postgresql-indexes-and-foreign-keys/)
+(otherwise, we'd need to scan the whole table) for subsequent update or
+deletion.
+
+Here's an example where we add a new column with a foreign key
+constraint. Note it includes `index: true` to create an index for it.
+
+```ruby
+class Migration < ActiveRecord::Migration
+
+ def change
+ add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade }
+ end
+end
+```
+
+When adding a foreign-key constraint to an existing column, we
+have to employ `add_concurrent_foreign_key` and `add_concurrent_index`
+instead of `add_reference`.
+
## Adding Columns With Default Values
When adding columns with default values you must use the method
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index e1e13474b75..53dfe6774e9 100644
--- a/doc/development/new_fe_guide/development/testing.md
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -133,3 +133,27 @@ afterEach(() => {
vm.$destroy();
});
```
+## Testing with older browsers
+
+Some regressions only affect a specific browser version. We can install and test in particular browsers with either Firefox or Browserstack using the following steps:
+
+
+### Browserstack
+
+[Browserstack](https://www.browserstack.com/) allows you to test more than 1200 mobile devices and browsers.
+You can use it directly through the [live app](https://www.browserstack.com/live) or you can install the [chrome extension](https://chrome.google.com/webstore/detail/browserstack/nkihdmlheodkdfojglpcjjmioefjahjb) for easy access.
+You can find the credentials on 1Password, under `frontendteam@gitlab.com`.
+
+### Firefox
+
+#### macOS
+You can download any older version of Firefox from the releases FTP server, https://ftp.mozilla.org/pub/firefox/releases/
+
+1. From the website, select a version, in this case `50.0.1`.
+2. Go to the mac folder.
+3. Select your preferred language, you will find the dmg package inside, download it.
+4. Drag and drop the application to any other folder but the `Applications` folder.
+5. Rename the application to something like `Firefox_Old`.
+6. Move the application to the `Applications` folder.
+7. Open up a terminal and run `/Applications/Firefox_Old.app/Contents/MacOS/firefox-bin -profilemanager` to create a new profile specific to that Firefox version.
+8. Once the profile has been created, quit the app, and run it again like normal. You now have a working older Firefox version.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index ea01d88d85f..a310f12b29e 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -153,7 +153,7 @@ page](https://golang.org/dl).
# Remove former Go installation folder
sudo rm -rf /usr/local/go
-
+
curl --remote-name --progress https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
echo 'fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035 go1.10.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
@@ -494,11 +494,11 @@ Make GitLab start on boot:
### Install Gitaly
# Fetch Gitaly source with Git and compile with Go
- sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production
+ sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
You can specify a different Git repository by providing it as an extra parameter:
- sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,https://example.com/gitaly.git]" RAILS_ENV=production
+ sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
Next, make sure gitaly configured:
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index bd199b55a61..6856544ae1b 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -234,6 +234,8 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji
Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup:
+ Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support.
+
Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
:zap: You can use emoji anywhere GFM is supported. :v:
@@ -244,6 +246,8 @@ If you are new to this, don't be :fearful:. You can easily join the emoji :famil
Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup:
+Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support.
+
### Special GitLab References
GFM recognizes special references.
@@ -259,6 +263,7 @@ GFM will recognize the following:
| `@user_name` | specific user |
| `@group_name` | specific group |
| `@all` | entire team |
+| `namespace/project>` | project |
| `#12345` | issue |
| `!123` | merge request |
| `$123` | snippet |
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index 50e051e25a0..925b969eebe 100644
--- a/doc/user/project/img/issue_board.png
+++ b/doc/user/project/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 8e486318980..77fa517b5b1 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1122,7 +1122,6 @@ X-Gitlab-Event: Build Hook
},
"repository": {
"name": "gitlab_test",
- "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
"description": "Atque in sunt eos similique dolores voluptatem.",
"homepage": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
"git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 49b49271cff..0e847be79c2 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -119,10 +119,10 @@ Issue Board, that is, create or delete lists and drag issues from one list to an
## Issue Board terminology
- **Issue Board** - Each board represents a unique view for your issues. It can have multiple lists with each list consisting of issues represented by cards.
-- **List** - A column on the issue board that displays issues matching certain attributes. In addition to the default lists of 'Backlog' and 'Closed' issue, each additional list will show issues matching your chosen label or assignee. On the top of that list you can see the number of issues that belong to it.
+- **List** - A column on the issue board that displays issues matching certain attributes. In addition to the default lists of 'Open' and 'Closed' issue, each additional list will show issues matching your chosen label or assignee. On the top of that list you can see the number of issues that belong to it.
- **Label list**: a list based on a label. It shows all opened issues with that label.
- **Assignee list**: a list which includes all issues assigned to a user.
- - **Backlog** (default): shows all open issues that do not belong to one of the other lists. Always appears as the leftmost list.
+ - **Open** (default): shows all open issues that do not belong to one of the other lists. Always appears as the leftmost list.
- **Closed** (default): shows all closed issues. Always appears as the rightmost list.
- **Card** - A box in the list that represents an individual issue. The information you can see on a card consists of the issue number, the issue title, the assignee, and the labels associated with the issue. You can drag cards from one list to another to change their label or assignee from that of the source list to that of the destination list.
@@ -353,9 +353,9 @@ To remove an assignee list, just as with a label list, click the trash icon.
When dragging issues between lists, different behavior occurs depending on the source list and the target list.
-| | To Backlog | To Closed | To label `B` list | To assignee `Bob` list |
+| | To Open | To Closed | To label `B` list | To assignee `Bob` list |
| --- | --- | --- | --- | --- |
-| From Backlog | - | Issue closed | `B` added | `Bob` assigned |
+| From Open | - | Issue closed | `B` added | `Bob` assigned |
| From Closed | Issue reopened | - | Issue reopened<br/>`B` added | Issue reopened<br/>`Bob` assigned |
| From label `A` list | `A` removed | Issue closed | `A` removed<br/>`B` added | `Bob` assigned |
| From assignee `Alice` list | `Alice` unassigned | Issue closed | `B` added | `Alice` unassigned<br/>`Bob` assigned |
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 86ecf33ed31..43ca498d006 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -43,8 +43,7 @@ A. Consider you are a software developer working in a team:
1. You checkout a new branch, and submit your changes through a merge request
1. You gather feedback from your team
-1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html) **[STARTER]**
-1. You build and test your changes with GitLab CI/CD
+1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD
1. You request the approval from your manager
1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#manual-actions) for GitLab CI/CD
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 0ef8eddad20..8fdfd2a6f4d 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -1,7 +1,7 @@
# GitLab quick actions
-Quick actions are textual shortcuts for common actions on issues or merge
-requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
+Quick actions are textual shortcuts for common actions on issues, merge requests
+or commits that are usually done by clicking buttons or dropdowns in GitLab's UI.
You can enter these commands while creating a new issue or merge request, and
in comments. Each command should be on a separate line in order to be properly
detected and executed. The commands are removed from the issue, merge request or
@@ -39,7 +39,8 @@ do.
| `/board_move ~column` | Move issue to column on the board |
| `/duplicate #issue` | Closes this issue and marks it as a duplicate of another issue |
| `/move path/to/project` | Moves issue to another project |
+| `/tag v1.2.3 <message>` | Tags a commit with a given tag name and optional message |
| `/tableflip` | Append the comment with `(╯°□°)╯︵ ┻━┻` |
| `/shrug` | Append the comment with `¯\_(ツ)_/¯` |
| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request |
-| `/confidential` | Makes the issue confidential | \ No newline at end of file
+| `/confidential` | Makes the issue confidential |
diff --git a/doc/user/project/web_ide/img/admin_clientside_evaluation.png b/doc/user/project/web_ide/img/admin_clientside_evaluation.png
new file mode 100644
index 00000000000..a930490398b
--- /dev/null
+++ b/doc/user/project/web_ide/img/admin_clientside_evaluation.png
Binary files differ
diff --git a/doc/user/project/web_ide/img/clientside_evaluation.png b/doc/user/project/web_ide/img/clientside_evaluation.png
new file mode 100644
index 00000000000..bd04d3d644b
--- /dev/null
+++ b/doc/user/project/web_ide/img/clientside_evaluation.png
Binary files differ
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 511ac2d7e79..16969b2c527 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -72,5 +72,39 @@ leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
of branches. You will need to commit or discard all your changes before
switching to a different branch.
+## Client Side Evaluation
+
+> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19764) [GitLab Core][ce] 11.2.
+
+The Web IDE can be used to preview JavaScript projects right in the browser.
+This feature uses CodeSandbox to compile and bundle the JavaScript used to
+preview the web application. On public projects, an `Open in CodeSandbox`
+button is visible which will transfer the contents of the project into a
+CodeSandbox project to share with others.
+**Note** this button is not visible on private or internal projects.
+
+![Web IDE Client Side Evaluation](img/clientside_evaluation.png)
+
+### Enabling Client Side Evaluation
+
+The Client Side Evaluation feature needs to be enabled in the GitLab instances
+admin settings. Client Side Evaluation is enabled for all projects on
+GitLab.com
+
+![Admin Client Side Evaluation setting](img/admin_clientside_evaluation.png)
+
+Once it has been enabled in application settings, projects with a
+`package.json` file and a `main` entry point can be previewed inside of the Web
+IDE. An example `package.json` is below.
+
+```json
+{
+ "main": "index.js",
+ "dependencies": {
+ "vue": "latest"
+ }
+}
+```
+
[ce]: https://about.gitlab.com/pricing/
[ee]: https://about.gitlab.com/pricing/
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 27f28e1df93..b6393fdef19 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -856,7 +856,7 @@ module API
class NotificationSetting < Grape::Entity
expose :level
expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do
- ::NotificationSetting::EMAIL_EVENTS.each do |event|
+ ::NotificationSetting.email_events.each do |event|
expose event
end
end
@@ -1080,6 +1080,10 @@ module API
expose :filename, :size
end
+ class JobArtifact < Grape::Entity
+ expose :file_type, :size, :filename, :file_format
+ end
+
class JobBasic < Grape::Entity
expose :id, :status, :stage, :name, :ref, :tag, :coverage
expose :created_at, :started_at, :finished_at
@@ -1094,7 +1098,9 @@ module API
end
class Job < JobBasic
+ # artifacts_file is included in job_artifacts, but kept for backward compatibility (remove in api/v5)
expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? }
+ expose :job_artifacts, as: :artifacts, using: JobArtifact
expose :runner, with: Runner
expose :artifacts_expire_at
end
@@ -1153,7 +1159,7 @@ module API
class License < Grape::Entity
expose :key, :name, :nickname
- expose :featured, as: :popular
+ expose :popular?, as: :popular
expose :url, as: :html_url
expose(:source_url) { |license| license.meta['source'] }
expose(:description) { |license| license.meta['description'] }
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 10c6e565f09..fc8c52085ab 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -38,7 +38,7 @@ module API
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
- builds = builds.preload(:user, :job_artifacts_archive, :runner, pipeline: :project)
+ builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, pipeline: :project)
present paginate(builds), with: Entities::Job
end
@@ -54,7 +54,7 @@ module API
pipeline = user_project.pipelines.find(params[:pipeline_id])
builds = pipeline.builds
builds = filter_builds(builds, params[:scope])
- builds = builds.preload(:job_artifacts_archive, project: [:namespace])
+ builds = builds.preload(:job_artifacts_archive, :job_artifacts, project: [:namespace])
present paginate(builds), with: Entities::Job
end
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index 0266bf2f717..bf0d6b9e434 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -23,7 +23,7 @@ module API
params do
optional :level, type: String, desc: 'The global notification level'
optional :notification_email, type: String, desc: 'The email address to send notifications'
- NotificationSetting::EMAIL_EVENTS.each do |event|
+ NotificationSetting.email_events.each do |event|
optional event, type: Boolean, desc: 'Enable/disable this notification'
end
end
@@ -50,7 +50,9 @@ module API
end
end
- %w[group project].each do |source_type|
+ [Group, Project].each do |source_class|
+ source_type = source_class.name.underscore
+
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
@@ -73,7 +75,7 @@ module API
end
params do
optional :level, type: String, desc: "The #{source_type} notification level"
- NotificationSetting::EMAIL_EVENTS.each do |event|
+ NotificationSetting.email_events(source_class).each do |event|
optional event, type: Boolean, desc: 'Enable/disable this notification'
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 33a9646ac3b..79736107bbb 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -123,6 +123,39 @@ module API
not_found!
end
end
+
+ desc 'Get the common ancestor between commits' do
+ success Entities::Commit
+ end
+ params do
+ # For now we just support 2 refs passed, but `merge-base` supports
+ # multiple defining this as an Array instead of 2 separate params will
+ # make sure we don't need to deprecate this API in favor of one
+ # supporting multiple commits when this functionality gets added to
+ # Gitaly
+ requires :refs, type: Array[String]
+ end
+ get ':id/repository/merge_base' do
+ refs = params[:refs]
+
+ unless refs.size == 2
+ render_api_error!('Provide exactly 2 refs', 400)
+ end
+
+ merge_base = Gitlab::Git::MergeBase.new(user_project.repository, refs)
+
+ if merge_base.unknown_refs.any?
+ ref_noun = 'ref'.pluralize(merge_base.unknown_refs.size)
+ message = "Could not find #{ref_noun}: #{merge_base.unknown_refs.join(', ')}"
+ render_api_error!(message, 400)
+ end
+
+ if merge_base.commit
+ present merge_base.commit, with: Entities::Commit
+ else
+ not_found!("Merge Base")
+ end
+ end
end
end
end
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 41862768a3f..927baaea652 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -16,31 +16,8 @@ module API
gitlab_version: 8.15
}
}.freeze
- PROJECT_TEMPLATE_REGEX =
- %r{[\<\{\[]
- (project|description|
- one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
- [\>\}\]]}xi.freeze
- YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
- FULLNAME_TEMPLATE_REGEX =
- %r{[\<\{\[]
- (fullname|name\sof\s(author|copyright\sowner))
- [\>\}\]]}xi.freeze
helpers do
- def parsed_license_template
- # We create a fresh Licensee::License object since we'll modify its
- # content in place below.
- template = Licensee::License.new(params[:name])
-
- template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
- template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
-
- fullname = params[:fullname].presence || current_user.try(:name)
- template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
- template
- end
-
def render_response(template_type, template)
not_found!(template_type.to_s.singularize) unless template
present template, with: Entities::Template
@@ -56,11 +33,12 @@ module API
use :pagination
end
get "templates/licenses" do
- options = {
- featured: declared(params)[:popular].present? ? true : nil
- }
- licences = ::Kaminari.paginate_array(Licensee::License.all(options))
- present paginate(licences), with: Entities::License
+ popular = declared(params)[:popular]
+ popular = to_boolean(popular) if popular.present?
+
+ templates = LicenseTemplateFinder.new(popular: popular).execute
+
+ present paginate(::Kaminari.paginate_array(templates)), with: ::API::Entities::License
end
desc 'Get the text for a specific license' do
@@ -71,9 +49,15 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do
- not_found!('License') unless Licensee::License.find(declared(params)[:name])
+ templates = LicenseTemplateFinder.new.execute
+ template = templates.find { |template| template.key == params[:name] }
+
+ not_found!('License') unless template.present?
- template = parsed_license_template
+ template.resolve!(
+ project_name: params[:project].presence,
+ fullname: params[:fullname].presence || current_user&.name
+ )
present template, with: ::API::Entities::License
end
diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb
new file mode 100644
index 00000000000..83cf45097ed
--- /dev/null
+++ b/lib/banzai/filter/project_reference_filter.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that replaces project references with links.
+ class ProjectReferenceFilter < ReferenceFilter
+ self.reference_type = :project
+
+ # Public: Find `namespace/project>` project references in text
+ #
+ # ProjectReferenceFilter.references_in(text) do |match, project|
+ # "<a href=...>#{project}></a>"
+ # end
+ #
+ # text - String text to search.
+ #
+ # Yields the String match, and the String project name.
+ #
+ # Returns a String replaced with the return of the block.
+ def self.references_in(text)
+ text.gsub(Project.markdown_reference_pattern) do |match|
+ yield match, "#{$~[:namespace]}/#{$~[:project]}"
+ end
+ end
+
+ def call
+ ref_pattern = Project.markdown_reference_pattern
+ ref_pattern_start = /\A#{ref_pattern}\z/
+
+ nodes.each do |node|
+ if text_node?(node)
+ replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ project_link_filter(content)
+ end
+ elsif element_node?(node)
+ yield_valid_link(node) do |link, inner_html|
+ if link =~ ref_pattern_start
+ replace_link_node_with_href(node, link) do
+ project_link_filter(link, link_content: inner_html)
+ end
+ end
+ end
+ end
+ end
+
+ doc
+ end
+
+ # Replace `namespace/project>` project references in text with links to the referenced
+ # project page.
+ #
+ # text - String text to replace references in.
+ # link_content - Original content of the link being replaced.
+ #
+ # Returns a String with `namespace/project>` references replaced with links. All links
+ # have `gfm` and `gfm-project` class names attached for styling.
+ def project_link_filter(text, link_content: nil)
+ self.class.references_in(text) do |match, project_path|
+ cached_call(:banzai_url_for_object, match, path: [Project, project_path.downcase]) do
+ if project = projects_hash[project_path.downcase]
+ link_to_project(project, link_content: link_content) || match
+ else
+ match
+ end
+ end
+ end
+ end
+
+ # Returns a Hash containing all Project objects for the project
+ # references in the current document.
+ #
+ # The keys of this Hash are the project paths, the values the
+ # corresponding Project objects.
+ def projects_hash
+ @projects ||= Project.eager_load(:route, namespace: [:route])
+ .where_full_path_in(projects)
+ .index_by(&:full_path)
+ .transform_keys(&:downcase)
+ end
+
+ # Returns all projects referenced in the current document.
+ def projects
+ refs = Set.new
+
+ nodes.each do |node|
+ node.to_html.scan(Project.markdown_reference_pattern) do
+ refs << "#{$~[:namespace]}/#{$~[:project]}"
+ end
+ end
+
+ refs.to_a
+ end
+
+ private
+
+ def urls
+ Gitlab::Routing.url_helpers
+ end
+
+ def link_class
+ reference_class(:project)
+ end
+
+ def link_to_project(project, link_content: nil)
+ url = urls.project_url(project, only_path: context[:only_path])
+ data = data_attribute(project: project.id)
+ content = link_content || project.to_reference_with_postfix
+
+ link_tag(url, data, content, project.name)
+ end
+
+ def link_tag(url, data, link_content, title)
+ %(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{link_content}</a>)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 5dab80dd3eb..e9be05e174e 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -36,6 +36,7 @@ module Banzai
def self.reference_filters
[
Filter::UserReferenceFilter,
+ Filter::ProjectReferenceFilter,
Filter::IssueReferenceFilter,
Filter::ExternalIssueReferenceFilter,
Filter::MergeRequestReferenceFilter,
diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb
new file mode 100644
index 00000000000..b4e3a55b4f1
--- /dev/null
+++ b/lib/banzai/reference_parser/project_parser.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Banzai
+ module ReferenceParser
+ class ProjectParser < BaseParser
+ include Gitlab::Utils::StrongMemoize
+
+ self.reference_type = :project
+
+ def references_relation
+ Project
+ end
+
+ private
+
+ # Returns an Array of Project ids that can be read by the given user.
+ #
+ # user - The User for which to check the projects
+ def readable_project_ids_for(user)
+ @project_ids_by_user ||= {}
+ @project_ids_by_user[user] ||=
+ Project.public_or_visible_to_user(user).where("projects.id IN (?)", @projects_for_nodes.values.map(&:id)).pluck(:id)
+ end
+
+ def can_read_reference?(user, ref_project, node)
+ readable_project_ids_for(user).include?(ref_project.try(:id))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index f39b3b6eb5b..7f012312819 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -58,7 +58,6 @@ module Gitlab
if Database.postgresql?
options = options.merge({ algorithm: :concurrently })
- disable_statement_timeout
end
if index_exists?(table_name, column_name, options)
@@ -66,7 +65,9 @@ module Gitlab
return
end
- add_index(table_name, column_name, options)
+ disable_statement_timeout do
+ add_index(table_name, column_name, options)
+ end
end
# Removes an existed index, concurrently when supported
@@ -87,7 +88,6 @@ module Gitlab
if supports_drop_index_concurrently?
options = options.merge({ algorithm: :concurrently })
- disable_statement_timeout
end
unless index_exists?(table_name, column_name, options)
@@ -95,7 +95,9 @@ module Gitlab
return
end
- remove_index(table_name, options.merge({ column: column_name }))
+ disable_statement_timeout do
+ remove_index(table_name, options.merge({ column: column_name }))
+ end
end
# Removes an existing index, concurrently when supported
@@ -116,7 +118,6 @@ module Gitlab
if supports_drop_index_concurrently?
options = options.merge({ algorithm: :concurrently })
- disable_statement_timeout
end
unless index_exists_by_name?(table_name, index_name)
@@ -124,7 +125,9 @@ module Gitlab
return
end
- remove_index(table_name, options.merge({ name: index_name }))
+ disable_statement_timeout do
+ remove_index(table_name, options.merge({ name: index_name }))
+ end
end
# Only available on Postgresql >= 9.2
@@ -171,8 +174,6 @@ module Gitlab
on_delete = 'SET NULL' if on_delete == :nullify
end
- disable_statement_timeout
-
key_name = concurrent_foreign_key_name(source, column)
unless foreign_key_exists?(source, target, column: column)
@@ -199,7 +200,9 @@ module Gitlab
# while running.
#
# Note this is a no-op in case the constraint is VALID already
- execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
+ disable_statement_timeout do
+ execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
+ end
end
def foreign_key_exists?(source, target = nil, column: nil)
@@ -224,8 +227,48 @@ module Gitlab
# Long-running migrations may take more than the timeout allowed by
# the database. Disable the session's statement timeout to ensure
# migrations don't get killed prematurely. (PostgreSQL only)
+ #
+ # There are two possible ways to disable the statement timeout:
+ #
+ # - Per transaction (this is the preferred and default mode)
+ # - Per connection (requires a cleanup after the execution)
+ #
+ # When using a per connection disable statement, code must be inside
+ # a block so we can automatically execute `RESET ALL` after block finishes
+ # otherwise the statement will still be disabled until connection is dropped
+ # or `RESET ALL` is executed
def disable_statement_timeout
- execute('SET statement_timeout TO 0') if Database.postgresql?
+ # bypass disabled_statement logic when not using postgres, but still execute block when one is given
+ unless Database.postgresql?
+ if block_given?
+ yield
+ end
+
+ return
+ end
+
+ if block_given?
+ begin
+ execute('SET statement_timeout TO 0')
+
+ yield
+ ensure
+ execute('RESET ALL')
+ end
+ else
+ unless transaction_open?
+ raise <<~ERROR
+ Cannot call disable_statement_timeout() without a transaction open or outside of a transaction block.
+ If you don't want to use a transaction wrap your code in a block call:
+
+ disable_statement_timeout { # code that requires disabled statement here }
+
+ This will make sure statement_timeout is disabled before and reset after the block execution is finished.
+ ERROR
+ end
+
+ execute('SET LOCAL statement_timeout TO 0')
+ end
end
def true_value
@@ -367,30 +410,30 @@ module Gitlab
'in the body of your migration class'
end
- disable_statement_timeout
-
- transaction do
- if limit
- add_column(table, column, type, default: nil, limit: limit)
- else
- add_column(table, column, type, default: nil)
+ disable_statement_timeout do
+ transaction do
+ if limit
+ add_column(table, column, type, default: nil, limit: limit)
+ else
+ add_column(table, column, type, default: nil)
+ end
+
+ # Changing the default before the update ensures any newly inserted
+ # rows already use the proper default value.
+ change_column_default(table, column, default)
end
- # Changing the default before the update ensures any newly inserted
- # rows already use the proper default value.
- change_column_default(table, column, default)
- end
-
- begin
- update_column_in_batches(table, column, default, &block)
+ begin
+ update_column_in_batches(table, column, default, &block)
- change_column_null(table, column, false) unless allow_null
- # We want to rescue _all_ exceptions here, even those that don't inherit
- # from StandardError.
- rescue Exception => error # rubocop: disable all
- remove_column(table, column)
+ change_column_null(table, column, false) unless allow_null
+ # We want to rescue _all_ exceptions here, even those that don't inherit
+ # from StandardError.
+ rescue Exception => error # rubocop: disable all
+ remove_column(table, column)
- raise error
+ raise error
+ end
end
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 55236a1122f..2913a3e416d 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -10,9 +10,11 @@ module Gitlab
TAG_REF_PREFIX = "refs/tags/".freeze
BRANCH_REF_PREFIX = "refs/heads/".freeze
- CommandError = Class.new(StandardError)
- CommitError = Class.new(StandardError)
- OSError = Class.new(StandardError)
+ BaseError = Class.new(StandardError)
+ CommandError = Class.new(BaseError)
+ CommitError = Class.new(BaseError)
+ OSError = Class.new(BaseError)
+ UnknownRef = Class.new(BaseError)
class << self
include Gitlab::EncodingHelper
diff --git a/lib/gitlab/git/merge_base.rb b/lib/gitlab/git/merge_base.rb
new file mode 100644
index 00000000000..b27f7038c26
--- /dev/null
+++ b/lib/gitlab/git/merge_base.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ class MergeBase
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(repository, refs)
+ @repository, @refs = repository, refs
+ end
+
+ # Returns the SHA of the first common ancestor
+ def sha
+ if unknown_refs.any?
+ raise UnknownRef, "Can't find merge base for unknown refs: #{unknown_refs.inspect}"
+ end
+
+ strong_memoize(:sha) do
+ @repository.merge_base(*commits_for_refs)
+ end
+ end
+
+ # Returns the merge base as a Gitlab::Git::Commit
+ def commit
+ return unless sha
+
+ @commit ||= @repository.commit_by(oid: sha)
+ end
+
+ # Returns the refs passed on initialization that aren't found in
+ # the repository, and thus cannot be used to find a merge base.
+ def unknown_refs
+ @unknown_refs ||= Hash[@refs.zip(commits_for_refs)]
+ .select { |ref, commit| commit.nil? }.keys
+ end
+
+ private
+
+ def commits_for_refs
+ @commits_for_refs ||= @repository.commits_by(oids: @refs)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 3e11355435b..e9c901f8592 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -366,18 +366,9 @@ module Gitlab
end
end
- # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233
def new_commits(newrev)
- gitaly_migrate(:new_commits) do |is_enabled|
- if is_enabled
- gitaly_ref_client.list_new_commits(newrev)
- else
- refs = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- rev_list(including: newrev, excluding: :all).split("\n").map(&:strip)
- end
-
- Gitlab::Git::Commit.batch_by_oid(self, refs)
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_client.list_new_commits(newrev)
end
end
diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb
index 65eb5cc18cf..752a91fbb60 100644
--- a/lib/gitlab/git/repository_mirroring.rb
+++ b/lib/gitlab/git/repository_mirroring.rb
@@ -2,34 +2,7 @@ module Gitlab
module Git
module RepositoryMirroring
def remote_branches(remote_name)
- gitaly_migrate(:ref_find_all_remote_branches) do |is_enabled|
- if is_enabled
- gitaly_ref_client.remote_branches(remote_name)
- else
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- rugged_remote_branches(remote_name)
- end
- end
- end
- end
-
- private
-
- def rugged_remote_branches(remote_name)
- branches = []
-
- rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref|
- name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '')
-
- begin
- target_commit = Gitlab::Git::Commit.find(self, ref.target.oid)
- branches << Gitlab::Git::Branch.new(self, name, ref.target, target_commit)
- rescue Rugged::ReferenceError
- # Omit invalid branch
- end
- end
-
- branches
+ gitaly_ref_client.remote_branches(remote_name)
end
end
end
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index 343487bc361..b8213929c6a 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -22,7 +22,8 @@ module Gitlab
'tr_TR' => 'Türkçe',
'id_ID' => 'Bahasa Indonesia',
'fil_PH' => 'Filipino',
- 'pl_PL' => 'Polski'
+ 'pl_PL' => 'Polski',
+ 'cs_CZ' => 'Čeština'
}.freeze
def available_locales
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 091e81028bb..81807ed659c 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -47,7 +47,7 @@ module Gitlab
end
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:, excluded_keys: [])
- @relation_name = OVERRIDES[relation_sym] || relation_sym
+ @relation_name = self.class.overrides[relation_sym] || relation_sym
@relation_hash = relation_hash.except('noteable_id')
@members_mapper = members_mapper
@user = user
@@ -76,6 +76,10 @@ module Gitlab
generate_imported_object
end
+ def self.overrides
+ OVERRIDES
+ end
+
private
def setup_models
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index b2d75aac1d0..5b68e4470cd 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -1,3 +1,5 @@
+require 'toml-rb'
+
module Gitlab
module SetupHelper
class << self
@@ -9,7 +11,7 @@ module Gitlab
# because it uses a Unix socket.
# For development and testing purposes, an extra storage is added to gitaly,
# which is not known to Rails, but must be explicitly stubbed.
- def gitaly_configuration_toml(gitaly_dir, gitaly_ruby: true)
+ def gitaly_configuration_toml(gitaly_dir, storage_paths, gitaly_ruby: true)
storages = []
address = nil
@@ -24,10 +26,7 @@ module Gitlab
address = val['gitaly_address']
end
- # https://gitlab.com/gitlab-org/gitaly/issues/1238
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- storages << { name: key, path: val.legacy_disk_path }
- end
+ storages << { name: key, path: storage_paths[key] }
end
if Rails.env.test?
@@ -44,12 +43,12 @@ module Gitlab
end
# rubocop:disable Rails/Output
- def create_gitaly_configuration(dir, force: false)
+ def create_gitaly_configuration(dir, storage_paths, force: false)
config_path = File.join(dir, 'config.toml')
FileUtils.rm_f(config_path) if force
File.open(config_path, File::WRONLY | File::CREAT | File::EXCL) do |f|
- f.puts gitaly_configuration_toml(dir)
+ f.puts gitaly_configuration_toml(dir, storage_paths)
end
rescue Errno::EEXIST
puts "Skipping config.toml generation:"
diff --git a/lib/gitlab/template/finders/base_template_finder.rb b/lib/gitlab/template/finders/base_template_finder.rb
index 473b05257c6..a5105439b12 100644
--- a/lib/gitlab/template/finders/base_template_finder.rb
+++ b/lib/gitlab/template/finders/base_template_finder.rb
@@ -21,7 +21,7 @@ module Gitlab
def category_directory(category)
return @base_dir unless category.present?
- @base_dir + @categories[category]
+ File.join(@base_dir, @categories[category])
end
class << self
diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb
index 33f07fa0120..29bc2393ff9 100644
--- a/lib/gitlab/template/finders/repo_template_finder.rb
+++ b/lib/gitlab/template/finders/repo_template_finder.rb
@@ -27,7 +27,7 @@ module Gitlab
directory = select_directory(file_name)
raise FileNotFoundError if directory.nil?
- category_directory(directory) + file_name
+ File.join(category_directory(directory), file_name)
end
def list_files_for(dir)
@@ -37,8 +37,8 @@ module Gitlab
entries = @repository.tree(:head, dir).entries
- names = entries.map(&:name)
- names.select { |f| f =~ self.class.filter_regex(@extension) }
+ paths = entries.map(&:path)
+ paths.select { |f| f =~ self.class.filter_regex(@extension) }
end
private
@@ -47,10 +47,10 @@ module Gitlab
return [] unless @commit
# Insert root as directory
- directories = ["", @categories.keys]
+ directories = ["", *@categories.keys]
directories.find do |category|
- path = category_directory(category) + file_name
+ path = File.join(category_directory(category), file_name)
@repository.blob_at(@commit.id, path)
end
end
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index e9ca6404fe8..80de3d2ef51 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -1,13 +1,12 @@
namespace :gitlab do
namespace :gitaly do
desc "GitLab | Install or upgrade gitaly"
- task :install, [:dir, :repo] => :gitlab_environment do |t, args|
- require 'toml-rb'
-
+ task :install, [:dir, :storage_path, :repo] => :gitlab_environment do |t, args|
warn_user_is_not_gitlab
- unless args.dir.present?
- abort %(Please specify the directory where you want to install gitaly:\n rake "gitlab:gitaly:install[/home/git/gitaly]")
+ unless args.dir.present? && args.storage_path.present?
+ abort %(Please specify the directory where you want to install gitaly and the path for the default storage
+Usage: rake "gitlab:gitaly:install[/installation/dir,/storage/path]")
end
args.with_defaults(repo: 'https://gitlab.com/gitlab-org/gitaly.git')
@@ -27,7 +26,8 @@ namespace :gitlab do
"BUNDLE_PATH=#{Bundler.bundle_path}")
end
- Gitlab::SetupHelper.create_gitaly_configuration(args.dir)
+ storage_paths = { 'default' => args.storage_path }
+ Gitlab::SetupHelper.create_gitaly_configuration(args.dir, storage_paths)
Dir.chdir(args.dir) do
# In CI we run scripts/gitaly-test-build instead of this command
unless ENV['CI'].present?
@@ -35,17 +35,5 @@ namespace :gitlab do
end
end
end
-
- desc "GitLab | Print storage configuration in TOML format"
- task storage_config: :environment do
- require 'toml-rb'
-
- puts "# Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)}"
- puts "# This is in TOML format suitable for use in Gitaly's config.toml file."
-
- # Exclude gitaly-ruby configuration because that depends on the gitaly
- # installation directory.
- puts Gitlab::SetupHelper.gitaly_configuration_toml('', gitaly_ruby: false)
- end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bec60cf592a..b01a0068694 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -101,6 +101,9 @@ msgstr[1] ""
msgid "%{filePath} deleted"
msgstr ""
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr ""
@@ -640,6 +643,9 @@ msgstr ""
msgid "Authentication log"
msgstr ""
+msgid "Authentication method"
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1987,6 +1993,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Debug"
+msgstr ""
+
msgid "Dec"
msgstr ""
@@ -2169,6 +2178,9 @@ msgstr ""
msgid "Diffs|Something went wrong while fetching diff lines."
msgstr ""
+msgid "Direction"
+msgstr ""
+
msgid "Directory name"
msgstr ""
@@ -2391,6 +2403,9 @@ msgstr ""
msgid "Environments|You don't have any environments right now."
msgstr ""
+msgid "Error"
+msgstr ""
+
msgid "Error Reporting and Logging"
msgstr ""
@@ -2439,6 +2454,9 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
msgid "Estimated"
msgstr ""
@@ -2517,6 +2535,9 @@ msgstr ""
msgid "Failed to remove issue from board, please try again."
msgstr ""
+msgid "Failed to remove mirror."
+msgstr ""
+
msgid "Failed to remove the pipeline schedule"
msgstr ""
@@ -2717,6 +2738,9 @@ msgstr ""
msgid "Go back"
msgstr ""
+msgid "Go to"
+msgstr ""
+
msgid "Go to %{link_to_google_takeout}."
msgstr ""
@@ -2947,6 +2971,9 @@ msgstr ""
msgid "IDE|Review"
msgstr ""
+msgid "IP Address"
+msgstr ""
+
msgid "Identifier"
msgstr ""
@@ -3031,12 +3058,21 @@ msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
msgid "Incompatible Project"
msgstr ""
+msgid "Indicates whether this runner can pick jobs without tags"
+msgstr ""
+
msgid "Inline"
msgstr ""
+msgid "Input your repository URL"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -3103,6 +3139,30 @@ msgstr ""
msgid "Jobs"
msgstr ""
+msgid "Job|Browse"
+msgstr ""
+
+msgid "Job|Download"
+msgstr ""
+
+msgid "Job|Job artifacts"
+msgstr ""
+
+msgid "Job|Job has been erased"
+msgstr ""
+
+msgid "Job|Job has been erased by"
+msgstr ""
+
+msgid "Job|Keep"
+msgstr ""
+
+msgid "Job|The artifacts were removed"
+msgstr ""
+
+msgid "Job|The artifacts will be removed"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -3354,6 +3414,9 @@ msgstr ""
msgid "Maximum git storage failures"
msgstr ""
+msgid "Maximum job timeout"
+msgstr ""
+
msgid "May"
msgstr ""
@@ -3468,6 +3531,21 @@ msgstr ""
msgid "Milestones|Promote Milestone"
msgstr ""
+msgid "Mirror a repository"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -3528,6 +3606,9 @@ msgstr ""
msgid "Network"
msgstr ""
+msgid "Never"
+msgstr ""
+
msgid "New"
msgstr ""
@@ -3791,12 +3872,18 @@ msgstr ""
msgid "Only comments from the following commit are shown below"
msgstr ""
+msgid "Only mirror protected branches"
+msgstr ""
+
msgid "Only project members can comment."
msgstr ""
msgid "Oops, are you sure?"
msgstr ""
+msgid "Open"
+msgstr ""
+
msgid "Open in Xcode"
msgstr ""
@@ -3872,6 +3959,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Paused Runners don't accept new jobs"
+msgstr ""
+
msgid "Pending"
msgstr ""
@@ -4364,6 +4454,9 @@ msgstr ""
msgid "Promote to group label"
msgstr ""
+msgid "Protected"
+msgstr ""
+
msgid "Protip:"
msgstr ""
@@ -4379,6 +4472,9 @@ msgstr ""
msgid "Public pipelines"
msgstr ""
+msgid "Push"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -4576,6 +4672,9 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Run untagged jobs"
+msgstr ""
+
msgid "Runner token"
msgstr ""
@@ -4588,6 +4687,9 @@ msgstr ""
msgid "Runners can be placed on separate users, servers, and even on your local machine."
msgstr ""
+msgid "Runners page"
+msgstr ""
+
msgid "Running"
msgstr ""
@@ -4780,6 +4882,9 @@ msgstr ""
msgid "Set up Koding"
msgstr ""
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -5189,6 +5294,9 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
msgstr ""
@@ -5246,6 +5354,9 @@ msgstr ""
msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
msgstr ""
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>."
+msgstr ""
+
msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
msgstr ""
@@ -5402,12 +5513,21 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This runner will only run on pipelines triggered on protected branches"
+msgstr ""
+
msgid "This source diff could not be displayed because it is too large."
msgstr ""
+msgid "This timeout will take precedence when lower than Project-defined timeout"
+msgstr ""
+
msgid "This user has no identities"
msgstr ""
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
msgid "Time before an issue gets scheduled"
msgstr ""
@@ -5643,6 +5763,9 @@ msgstr ""
msgid "ToggleButton|Toggle Status: ON"
msgstr ""
+msgid "Token"
+msgstr ""
+
msgid "Too many changes to show."
msgstr ""
@@ -5721,9 +5844,15 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update now"
+msgstr ""
+
msgid "Update your group name, description, avatar, and other general settings."
msgstr ""
+msgid "Updating"
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -6063,6 +6192,9 @@ msgstr ""
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
+msgid "You can setup jobs to only use Runners with specific tags. Separate tags with commas."
+msgstr ""
+
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
diff --git a/locale/unfound_translations.rb b/locale/unfound_translations.rb
new file mode 100644
index 00000000000..0826d64049b
--- /dev/null
+++ b/locale/unfound_translations.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# Dynamic translations which needs to be marked by `N_` so they can be found by `rake gettext:find`, see:
+# https://github.com/grosser/gettext_i18n_rails#unfound-translations-with-rake-gettextfind
+
+# NotificationSetting.email_events
+N_('NotificationEvent|New note')
+N_('NotificationEvent|New issue')
+N_('NotificationEvent|Reopen issue')
+N_('NotificationEvent|Close issue')
+N_('NotificationEvent|Reassign issue')
+N_('NotificationEvent|New merge request')
+N_('NotificationEvent|Close merge request')
+N_('NotificationEvent|Reassign merge request')
+N_('NotificationEvent|Merge merge request')
+N_('NotificationEvent|Failed pipeline')
diff --git a/qa/qa/specs/features/project/auto_devops_spec.rb b/qa/qa/specs/features/project/auto_devops_spec.rb
index bc713b46d81..c2c3bef98e4 100644
--- a/qa/qa/specs/features/project/auto_devops_spec.rb
+++ b/qa/qa/specs/features/project/auto_devops_spec.rb
@@ -50,7 +50,7 @@ module QA
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_build('build', status: :success, wait: 600)
expect(pipeline).to have_build('test', status: :success, wait: 600)
- expect(pipeline).to have_build('production', status: :success, wait: 600)
+ expect(pipeline).to have_build('production', status: :success, wait: 1200)
end
end
end
diff --git a/rubocop/cop/migration/add_reference.rb b/rubocop/cop/migration/add_reference.rb
new file mode 100644
index 00000000000..4b67270c97a
--- /dev/null
+++ b/rubocop/cop/migration/add_reference.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that checks if a foreign key constraint is added and require a index for it
+ class AddReference < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = '`add_reference` requires `index: true`'
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ name = node.children[1]
+
+ return unless name == :add_reference
+
+ opts = node.children.last
+
+ add_offense(node, location: :selector) unless opts && opts.type == :hash
+
+ index_present = false
+
+ opts.each_node(:pair) do |pair|
+ index_present ||= index_enabled?(pair)
+ end
+
+ add_offense(node, location: :selector) unless index_present
+ end
+
+ private
+
+ def index_enabled?(pair)
+ hash_key_type(pair) == :sym && hash_key_name(pair) == :index && pair.children[1].true_type?
+ end
+
+ def hash_key_type(pair)
+ pair.children[0].type
+ end
+
+ def hash_key_name(pair)
+ pair.children[0].children[0]
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index aa7ae601f75..a427208cdab 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -11,6 +11,7 @@ require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
+require_relative 'cop/migration/add_reference'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
require_relative 'cop/migration/hash_index'
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 421ab006792..fbf116e533b 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -162,6 +162,10 @@ describe ApplicationController do
describe 'session expiration' do
controller(described_class) do
+ # The anonymous controller will report 401 and fail to run any actions.
+ # Normally, GitLab will just redirect you to sign in.
+ skip_before_action :authenticate_user!, only: :index
+
def index
render text: 'authenticated'
end
diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb
index e133950e684..a3356a86d4b 100644
--- a/spec/controllers/notification_settings_controller_spec.rb
+++ b/spec/controllers/notification_settings_controller_spec.rb
@@ -21,10 +21,11 @@ describe NotificationSettingsController do
end
context 'when authorized' do
+ let(:notification_setting) { user.notification_settings_for(source) }
let(:custom_events) do
events = {}
- NotificationSetting::EMAIL_EVENTS.each do |event|
+ NotificationSetting.email_events(source).each do |event|
events[event.to_s] = true
end
@@ -36,7 +37,7 @@ describe NotificationSettingsController do
end
context 'for projects' do
- let(:notification_setting) { user.notification_settings_for(project) }
+ let(:source) { project }
it 'creates notification setting' do
post :create,
@@ -67,7 +68,7 @@ describe NotificationSettingsController do
end
context 'for groups' do
- let(:notification_setting) { user.notification_settings_for(group) }
+ let(:source) { group }
it 'creates notification setting' do
post :create,
@@ -145,7 +146,7 @@ describe NotificationSettingsController do
let(:custom_events) do
events = {}
- NotificationSetting::EMAIL_EVENTS.each do |event|
+ notification_setting.email_events.each do |event|
events[event] = "true"
end
end
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index a81b2169b89..81c485fba1a 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -46,6 +46,13 @@ FactoryBot.define do
secret SecureRandom.hex
end
+ trait :favicon_upload do
+ model { build(:appearance) }
+ path { File.join(secret, filename) }
+ uploader "FaviconUploader"
+ secret SecureRandom.hex
+ end
+
trait :attachment_upload do
transient do
mount_point :attachment
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index a0af2dea3ad..baa2b1d8af5 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -44,7 +44,7 @@ describe 'Issue Boards', :js do
end
it 'creates default lists' do
- lists = ['Backlog', 'To Do', 'Doing', 'Closed']
+ lists = ['Open', 'To Do', 'Doing', 'Closed']
page.within(find('.board-blank-state')) do
click_button('Add default lists')
diff --git a/spec/features/commits/user_uses_slash_commands_spec.rb b/spec/features/commits/user_uses_slash_commands_spec.rb
new file mode 100644
index 00000000000..9a4b7bd2444
--- /dev/null
+++ b/spec/features/commits/user_uses_slash_commands_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Commit > User uses quick actions', :js do
+ include Spec::Support::Helpers::Features::NotesHelpers
+ include RepoHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:commit) { project.commit }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit project_commit_path(project, commit.id)
+ end
+
+ describe 'Tagging a commit' do
+ let(:tag_name) { 'v1.2.3' }
+ let(:tag_message) { 'Stable release' }
+ let(:truncated_commit_sha) { Commit.truncate_sha(commit.sha) }
+
+ it 'tags this commit' do
+ add_note("/tag #{tag_name} #{tag_message}")
+
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "tagged commit #{truncated_commit_sha}"
+ expect(page).to have_content tag_name
+
+ visit project_tag_path(project, tag_name)
+ expect(page).to have_content tag_name
+ expect(page).to have_content tag_message
+ expect(page).to have_content truncated_commit_sha
+ end
+
+ describe 'preview', :js do
+ it 'removes quick action from note and explains it' do
+ preview_note("/tag #{tag_name} #{tag_message}")
+
+ expect(page).not_to have_content '/tag'
+ expect(page).to have_content %{Tags this commit to #{tag_name} with "#{tag_message}"}
+ expect(page).to have_content tag_name
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
deleted file mode 100644
index bf60b18873c..00000000000
--- a/spec/features/issues/award_emoji_spec.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-require 'rails_helper'
-
-describe 'Awards Emoji' do
- let!(:project) { create(:project, :public) }
- let!(:user) { create(:user) }
- let(:issue) do
- create(:issue,
- assignees: [user],
- project: project)
- end
-
- context 'authorized user' do
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
-
- describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
- before do
- # The `heart_tip` emoji is not valid anymore so we need to skip validation
- issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
- visit project_issue_path(project, issue)
- wait_for_requests
- end
-
- # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
- it 'does not shows a 500 page', :js do
- expect(page).to have_text(issue.title)
- end
- end
-
- describe 'Click award emoji from issue#show' do
- let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
-
- before do
- visit project_issue_path(project, issue)
- wait_for_requests
- end
-
- it 'increments the thumbsdown emoji', :js do
- find('[data-name="thumbsdown"]').click
- wait_for_requests
- expect(thumbsdown_emoji).to have_text("1")
- end
-
- context 'click the thumbsup emoji' do
- it 'increments the thumbsup emoji', :js do
- find('[data-name="thumbsup"]').click
- wait_for_requests
- expect(thumbsup_emoji).to have_text("1")
- end
-
- it 'decrements the thumbsdown emoji', :js do
- expect(thumbsdown_emoji).to have_text("0")
- end
- end
-
- context 'click the thumbsdown emoji' do
- it 'increments the thumbsdown emoji', :js do
- find('[data-name="thumbsdown"]').click
- wait_for_requests
- expect(thumbsdown_emoji).to have_text("1")
- end
-
- it 'decrements the thumbsup emoji', :js do
- expect(thumbsup_emoji).to have_text("0")
- end
- end
-
- it 'toggles the smiley emoji on a note', :js do
- toggle_smiley_emoji(true)
-
- within('.note-body') do
- expect(find(emoji_counter)).to have_text("1")
- end
-
- toggle_smiley_emoji(false)
-
- within('.note-body') do
- expect(page).not_to have_selector(emoji_counter)
- end
- end
-
- context 'execute /award quick action' do
- it 'toggles the emoji award on noteable', :js do
- execute_quick_action('/award :100:')
-
- expect(find(noteable_award_counter)).to have_text("1")
-
- execute_quick_action('/award :100:')
-
- expect(page).not_to have_selector(noteable_award_counter)
- end
- end
- end
- end
-
- context 'unauthorized user', :js do
- before do
- visit project_issue_path(project, issue)
- end
-
- it 'has disabled emoji button' do
- expect(first('.award-control')[:class]).to have_text('disabled')
- end
- end
-
- def execute_quick_action(cmd)
- within('.js-main-target-form') do
- fill_in 'note[note]', with: cmd
- click_button 'Comment'
- end
-
- wait_for_requests
- end
-
- def thumbsup_emoji
- page.all(emoji_counter).first
- end
-
- def thumbsdown_emoji
- page.all(emoji_counter).last
- end
-
- def emoji_counter
- 'span.js-counter'
- end
-
- def noteable_award_counter
- ".awards .active"
- end
-
- def toggle_smiley_emoji(status)
- within('.note') do
- find('.note-emoji-button').click
- end
-
- unless status
- first('[data-name="smiley"]').click
- else
- find('[data-name="smiley"]').click
- end
-
- wait_for_requests
- end
-end
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
deleted file mode 100644
index e53a4ce49c7..00000000000
--- a/spec/features/issues/award_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'rails_helper'
-
-describe 'Issue awards', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public) }
- let(:issue) { create(:issue, project: project) }
-
- describe 'logged in' do
- before do
- sign_in(user)
- visit project_issue_path(project, issue)
- wait_for_requests
- end
-
- it 'adds award to issue' do
- first('.js-emoji-btn').click
- expect(page).to have_selector('.js-emoji-btn.active')
- expect(first('.js-emoji-btn')).to have_content '1'
-
- visit project_issue_path(project, issue)
- expect(first('.js-emoji-btn')).to have_content '1'
- end
-
- it 'removes award from issue' do
- first('.js-emoji-btn').click
- find('.js-emoji-btn.active').click
- expect(first('.js-emoji-btn')).to have_content '0'
-
- visit project_issue_path(project, issue)
- expect(first('.js-emoji-btn')).to have_content '0'
- end
-
- it 'only has one menu on the page' do
- first('.js-add-award').click
- expect(page).to have_selector('.emoji-menu')
-
- expect(page).to have_selector('.emoji-menu', count: 1)
- end
- end
-
- describe 'logged out' do
- before do
- visit project_issue_path(project, issue)
- wait_for_requests
- end
-
- it 'does not see award menu button' do
- expect(page).not_to have_selector('.js-award-holder')
- end
- end
-end
diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb
new file mode 100644
index 00000000000..afa425c2cec
--- /dev/null
+++ b/spec/features/issues/user_interacts_with_awards_spec.rb
@@ -0,0 +1,347 @@
+require 'spec_helper'
+
+describe 'User interacts with awards' do
+ let(:user) { create(:user) }
+
+ describe 'User interacts with awards in an issue', :js do
+ let(:issue) { create(:issue, project: project)}
+ let(:project) { create(:project) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit(project_issue_path(project, issue))
+ end
+
+ it 'toggles the thumbsup award emoji' do
+ page.within('.awards') do
+ thumbsup = page.first('.award-control')
+ thumbsup.click
+ thumbsup.hover
+
+ expect(page).to have_selector('.js-emoji-btn')
+ expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+ expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+
+ thumbsup = page.first('.award-control')
+ thumbsup.click
+ thumbsup.hover
+
+ expect(page).to have_selector('.award-control.js-emoji-btn')
+ expect(page.all('.award-control.js-emoji-btn').size).to eq(2)
+
+ page.all('.award-control.js-emoji-btn').each do |element|
+ expect(element['title']).to eq('')
+ end
+
+ expect(page.all('.award-control .js-counter')).to all(have_content('0'))
+
+ thumbsup = page.first('.award-control')
+ thumbsup.click
+ thumbsup.hover
+
+ expect(page).to have_selector('.js-emoji-btn')
+ expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+ expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+ end
+ end
+
+ it 'toggles a custom award emoji' do
+ page.within('.awards') do
+ page.find('.js-add-award').click
+ end
+
+ page.find('.emoji-menu.is-visible')
+
+ expect(page).to have_selector('.js-emoji-menu-search')
+ expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
+
+ page.within('.emoji-menu-content') do
+ emoji_button = page.first('.js-emoji-btn')
+ emoji_button.hover
+ emoji_button.click
+ end
+
+ page.within('.awards') do
+ expect(page).to have_selector('.js-emoji-btn')
+ expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+ expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+
+ expect do
+ page.find('.js-emoji-btn.active').click
+ wait_for_requests
+ end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2)
+ end
+ end
+
+ it 'shows the list of award emoji categories' do
+ page.within('.awards') do
+ page.find('.js-add-award').click
+ end
+
+ page.find('.emoji-menu.is-visible')
+
+ expect(page).to have_selector('.js-emoji-menu-search')
+ expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
+
+ fill_in('emoji-menu-search', with: 'hand')
+
+ page.within('.emoji-menu-content') do
+ expect(page).to have_selector('[data-name="raised_hand"]')
+ end
+ end
+
+ it 'adds an award emoji by a comment' do
+ page.within('.js-main-target-form') do
+ fill_in('note[note]', with: ':smile:')
+
+ click_button('Comment')
+ end
+
+ expect(page).to have_emoji('smile')
+ end
+
+ context 'when a project is archived' do
+ let(:project) { create(:project, :archived) }
+
+ it 'hides the add award button' do
+ page.within('.awards') do
+ expect(page).not_to have_css('.js-add-award')
+ end
+ end
+ end
+
+ context 'User interacts with awards on a note' do
+ let!(:note) { create(:note, noteable: issue, project: issue.project) }
+ let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') }
+
+ it 'shows the award on the note' do
+ page.within('.note-awards') do
+ expect(page).to have_emoji('100')
+ end
+ end
+
+ it 'allows adding a vote to an award' do
+ page.within('.note-awards') do
+ find('gl-emoji[data-name="100"]').click
+ end
+ wait_for_requests
+
+ expect(note.reload.award_emoji.size).to eq(2)
+ end
+
+ it 'allows adding a new emoji' do
+ page.within('.note-actions') do
+ find('a.js-add-award').click
+ end
+ page.within('.emoji-menu-content') do
+ find('gl-emoji[data-name="8ball"]').click
+ end
+ wait_for_requests
+
+ page.within('.note-awards') do
+ expect(page).to have_emoji('8ball')
+ end
+ expect(note.reload.award_emoji.size).to eq(2)
+ end
+
+ context 'when the project is archived' do
+ let(:project) { create(:project, :archived) }
+
+ it 'hides the buttons for adding new emoji' do
+ page.within('.note-awards') do
+ expect(page).not_to have_css('.award-menu-holder')
+ end
+
+ page.within('.note-actions') do
+ expect(page).not_to have_css('a.js-add-award')
+ end
+ end
+
+ it 'does not allow toggling existing emoji' do
+ page.within('.note-awards') do
+ find('gl-emoji[data-name="100"]').click
+ end
+ wait_for_requests
+
+ expect(note.reload.award_emoji.size).to eq(1)
+ end
+ end
+ end
+ end
+
+ describe 'User interacts with awards on an issue', :js do
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project) }
+
+ describe 'logged in' do
+ before do
+ sign_in(user)
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ it 'adds award to issue' do
+ first('.js-emoji-btn').click
+
+ expect(page).to have_selector('.js-emoji-btn.active')
+ expect(first('.js-emoji-btn')).to have_content '1'
+
+ visit project_issue_path(project, issue)
+
+ expect(first('.js-emoji-btn')).to have_content '1'
+ end
+
+ it 'removes award from issue' do
+ first('.js-emoji-btn').click
+ find('.js-emoji-btn.active').click
+
+ expect(first('.js-emoji-btn')).to have_content '0'
+
+ visit project_issue_path(project, issue)
+
+ expect(first('.js-emoji-btn')).to have_content '0'
+ end
+
+ it 'only has one menu on the page' do
+ first('.js-add-award').click
+
+ expect(page).to have_selector('.emoji-menu', count: 1)
+ end
+ end
+
+ describe 'logged out' do
+ before do
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ it 'does not see award menu button' do
+ expect(page).not_to have_selector('.js-award-holder')
+ end
+ end
+ end
+
+ describe 'Awards Emoji' do
+ let!(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, assignees: [user], project: project) }
+
+ context 'authorized user' do
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
+ before do
+ # The `heart_tip` emoji is not valid anymore so we need to skip validation
+ issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
+ it 'does not shows a 500 page', :js do
+ expect(page).to have_text(issue.title)
+ end
+ end
+
+ describe 'Click award emoji from issue#show' do
+ let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
+
+ before do
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ context 'click the thumbsdown emoji' do
+ it 'increments the thumbsdown emoji', :js do
+ find('[data-name="thumbsdown"]').click
+ wait_for_requests
+ expect(thumbsdown_emoji).to have_text("1")
+ end
+
+ it 'decrements the thumbsup emoji', :js do
+ expect(thumbsup_emoji).to have_text("0")
+ end
+ end
+
+ it 'toggles the smiley emoji on a note', :js do
+ toggle_smiley_emoji(true)
+
+ within('.note-body') do
+ expect(find(emoji_counter)).to have_text("1")
+ end
+
+ toggle_smiley_emoji(false)
+
+ within('.note-body') do
+ expect(page).not_to have_selector(emoji_counter)
+ end
+ end
+
+ context 'execute /award quick action' do
+ it 'toggles the emoji award on noteable', :js do
+ execute_quick_action('/award :100:')
+
+ expect(find(noteable_award_counter)).to have_text("1")
+
+ execute_quick_action('/award :100:')
+
+ expect(page).not_to have_selector(noteable_award_counter)
+ end
+ end
+ end
+ end
+
+ context 'unauthorized user', :js do
+ before do
+ visit project_issue_path(project, issue)
+ end
+
+ it 'has disabled emoji button' do
+ expect(first('.award-control')[:class]).to have_text('disabled')
+ end
+ end
+
+ def execute_quick_action(cmd)
+ within('.js-main-target-form') do
+ fill_in 'note[note]', with: cmd
+ click_button 'Comment'
+ end
+
+ wait_for_requests
+ end
+
+ def thumbsup_emoji
+ page.all(emoji_counter).first
+ end
+
+ def thumbsdown_emoji
+ page.all(emoji_counter).last
+ end
+
+ def emoji_counter
+ 'span.js-counter'
+ end
+
+ def noteable_award_counter
+ ".awards .active"
+ end
+
+ def toggle_smiley_emoji(status)
+ within('.note') do
+ find('.note-emoji-button').click
+ end
+
+ if !status
+ first('[data-name="smiley"]').click
+ else
+ find('[data-name="smiley"]').click
+ end
+
+ wait_for_requests
+ end
+ end
+end
diff --git a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
index c1608be402a..fd4175d5227 100644
--- a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
@@ -28,7 +28,7 @@ describe 'Merge request > User sees MR with deleted source branch', :js do
click_on 'Changes'
wait_for_requests
- expect(page).to have_selector('.diffs.tab-pane .nothing-here-block')
+ expect(page).to have_selector('.diffs.tab-pane .file-holder')
expect(page).to have_content('Source branch does not exist.')
end
end
diff --git a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
deleted file mode 100644
index 4d860893abe..00000000000
--- a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
+++ /dev/null
@@ -1,172 +0,0 @@
-require 'spec_helper'
-
-describe 'User interacts with awards in an issue', :js do
- let(:issue) { create(:issue, project: project)}
- let(:project) { create(:project) }
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
-
- visit(project_issue_path(project, issue))
- end
-
- it 'toggles the thumbsup award emoji' do
- page.within('.awards') do
- thumbsup = page.first('.award-control')
- thumbsup.click
- thumbsup.hover
-
- expect(page).to have_selector('.js-emoji-btn')
- expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
- expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
-
- thumbsup = page.first('.award-control')
- thumbsup.click
- thumbsup.hover
-
- expect(page).to have_selector('.award-control.js-emoji-btn')
- expect(page.all('.award-control.js-emoji-btn').size).to eq(2)
-
- page.all('.award-control.js-emoji-btn').each do |element|
- expect(element['title']).to eq('')
- end
-
- page.all('.award-control .js-counter').each do |element|
- expect(element).to have_content('0')
- end
-
- thumbsup = page.first('.award-control')
- thumbsup.click
- thumbsup.hover
-
- expect(page).to have_selector('.js-emoji-btn')
- expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
- expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
- end
- end
-
- it 'toggles a custom award emoji' do
- page.within('.awards') do
- page.find('.js-add-award').click
- end
-
- page.find('.emoji-menu.is-visible')
-
- expect(page).to have_selector('.js-emoji-menu-search')
- expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
-
- page.within('.emoji-menu-content') do
- emoji_button = page.first('.js-emoji-btn')
- emoji_button.hover
- emoji_button.click
- end
-
- page.within('.awards') do
- expect(page).to have_selector('.js-emoji-btn')
- expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
- expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
-
- expect do
- page.find('.js-emoji-btn.active').click
- wait_for_requests
- end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2)
- end
- end
-
- it 'shows the list of award emoji categories' do
- page.within('.awards') do
- page.find('.js-add-award').click
- end
-
- page.find('.emoji-menu.is-visible')
-
- expect(page).to have_selector('.js-emoji-menu-search')
- expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
-
- fill_in('emoji-menu-search', with: 'hand')
-
- page.within('.emoji-menu-content') do
- expect(page).to have_selector('[data-name="raised_hand"]')
- end
- end
-
- it 'adds an award emoji by a comment' do
- page.within('.js-main-target-form') do
- fill_in('note[note]', with: ':smile:')
-
- click_button('Comment')
- end
-
- expect(page).to have_emoji('smile')
- end
-
- context 'when a project is archived' do
- let(:project) { create(:project, :archived) }
-
- it 'hides the add award button' do
- page.within('.awards') do
- expect(page).not_to have_css('.js-add-award')
- end
- end
- end
-
- context 'awards on a note' do
- let!(:note) { create(:note, noteable: issue, project: issue.project) }
- let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') }
-
- it 'shows the award on the note' do
- page.within('.note-awards') do
- expect(page).to have_emoji('100')
- end
- end
-
- it 'allows adding a vote to an award' do
- page.within('.note-awards') do
- find('gl-emoji[data-name="100"]').click
- end
- wait_for_requests
-
- expect(note.reload.award_emoji.size).to eq(2)
- end
-
- it 'allows adding a new emoji' do
- page.within('.note-actions') do
- find('a.js-add-award').click
- end
- page.within('.emoji-menu-content') do
- find('gl-emoji[data-name="8ball"]').click
- end
- wait_for_requests
-
- page.within('.note-awards') do
- expect(page).to have_emoji('8ball')
- end
- expect(note.reload.award_emoji.size).to eq(2)
- end
-
- context 'when the project is archived' do
- let(:project) { create(:project, :archived) }
-
- it 'hides the buttons for adding new emoji' do
- page.within('.note-awards') do
- expect(page).not_to have_css('.award-menu-holder')
- end
-
- page.within('.note-actions') do
- expect(page).not_to have_css('a.js-add-award')
- end
- end
-
- it 'does not allow toggling existing emoji' do
- page.within('.note-awards') do
- find('gl-emoji[data-name="100"]').click
- end
- wait_for_requests
-
- expect(note.reload.award_emoji.size).to eq(1)
- end
- end
- end
-end
diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb
index aeed38aeb76..3925de6cfb9 100644
--- a/spec/features/projects/blobs/shortcuts_blob_spec.rb
+++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Blob shortcuts' do
+describe 'Blob shortcuts', :js do
include TreeHelper
let(:project) { create(:project, :public, :repository) }
let(:path) { project.repository.ls_files(project.repository.root_ref)[0] }
@@ -18,6 +18,7 @@ describe 'Blob shortcuts' do
describe 'pressing "y"' do
it 'redirects to permalink with commit sha' do
visit_blob
+ wait_for_requests
find('body').native.send_key('y')
@@ -27,6 +28,7 @@ describe 'Blob shortcuts' do
it 'maintains fragment hash when redirecting' do
fragment = "L1"
visit_blob(fragment)
+ wait_for_requests
find('body').native.send_key('y')
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index f56174fc85c..f3cf3a282e5 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -210,9 +210,10 @@ describe "User browses files" do
end
end
- context "when browsing a file content" do
+ context "when browsing a file content", :js do
before do
visit(tree_path_root_ref)
+ wait_for_requests
click_link(".gitignore")
end
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
index 0e9f83a16ce..dcb7b947c61 100644
--- a/spec/features/projects/files/user_deletes_files_spec.rb
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Files > User deletes files' do
+describe 'Projects > Files > User deletes files', :js do
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
@@ -19,6 +19,7 @@ describe 'Projects > Files > User deletes files' do
before do
project.add_maintainer(user)
visit(project_tree_path_root_ref)
+ wait_for_requests
end
it 'deletes the file', :js do
@@ -35,10 +36,11 @@ describe 'Projects > Files > User deletes files' do
end
end
- context 'when an user does not have write access' do
+ context 'when an user does not have write access', :js do
before do
project2.add_reporter(user)
visit(project2_tree_path_root_ref)
+ wait_for_requests
end
it 'deletes the file in a forked project', :js do
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index ccc1bc1bc10..9eb65ec159c 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Files > User edits files' do
+describe 'Projects > Files > User edits files', :js do
include ProjectForksHelper
let(:project) { create(:project, :repository, name: 'Shop') }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
@@ -29,13 +29,14 @@ describe 'Projects > Files > User edits files' do
end
end
- context 'when an user has write access' do
+ context 'when an user has write access', :js do
before do
project.add_maintainer(user)
visit(project_tree_path_root_ref)
+ wait_for_requests
end
- it 'inserts a content of a file', :js do
+ it 'inserts a content of a file' do
click_link('.gitignore')
find('.js-edit-blob').click
find('.file-editor', match: :first)
@@ -49,13 +50,14 @@ describe 'Projects > Files > User edits files' do
it 'does not show the edit link if a file is binary' do
binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png')
visit(project_blob_path(project, binary_file))
+ wait_for_requests
page.within '.content' do
expect(page).not_to have_link('edit')
end
end
- it 'commits an edited file', :js do
+ it 'commits an edited file' do
click_link('.gitignore')
find('.js-edit-blob').click
find('.file-editor', match: :first)
@@ -72,7 +74,7 @@ describe 'Projects > Files > User edits files' do
expect(page).to have_content('*.rbca')
end
- it 'commits an edited file to a new branch', :js do
+ it 'commits an edited file to a new branch' do
click_link('.gitignore')
find('.js-edit-blob').click
@@ -91,7 +93,7 @@ describe 'Projects > Files > User edits files' do
expect(page).to have_content('*.rbca')
end
- it 'shows the diff of an edited file', :js do
+ it 'shows the diff of an edited file' do
click_link('.gitignore')
find('.js-edit-blob').click
find('.file-editor', match: :first)
@@ -106,13 +108,14 @@ describe 'Projects > Files > User edits files' do
it_behaves_like 'unavailable for an archived project'
end
- context 'when an user does not have write access' do
+ context 'when an user does not have write access', :js do
before do
project2.add_reporter(user)
visit(project2_tree_path_root_ref)
+ wait_for_requests
end
- it 'inserts a content of a file in a forked project', :js do
+ it 'inserts a content of a file in a forked project' do
click_link('.gitignore')
find('.js-edit-blob').click
@@ -134,7 +137,7 @@ describe 'Projects > Files > User edits files' do
expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
end
- it 'commits an edited file in a forked project', :js do
+ it 'commits an edited file in a forked project' do
click_link('.gitignore')
find('.js-edit-blob').click
@@ -163,6 +166,7 @@ describe 'Projects > Files > User edits files' do
let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) }
before do
visit(project2_tree_path_root_ref)
+ wait_for_requests
end
it 'links to the forked project for editing' do
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
index 3a81e77c4ba..e3da28d73c3 100644
--- a/spec/features/projects/files/user_replaces_files_spec.rb
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Files > User replaces files' do
+describe 'Projects > Files > User replaces files', :js do
include DropzoneHelper
let(:fork_message) do
@@ -21,9 +21,10 @@ describe 'Projects > Files > User replaces files' do
before do
project.add_maintainer(user)
visit(project_tree_path_root_ref)
+ wait_for_requests
end
- it 'replaces an existed file with a new one', :js do
+ it 'replaces an existed file with a new one' do
click_link('.gitignore')
expect(page).to have_content('.gitignore')
@@ -47,9 +48,10 @@ describe 'Projects > Files > User replaces files' do
before do
project2.add_reporter(user)
visit(project2_tree_path_root_ref)
+ wait_for_requests
end
- it 'replaces an existed file with a new one in a forked project', :js do
+ it 'replaces an existed file with a new one in a forked project' do
click_link('.gitignore')
expect(page).to have_content('.gitignore')
diff --git a/spec/features/projects/remote_mirror_spec.rb b/spec/features/projects/remote_mirror_spec.rb
index 5259a8942dc..33e9b73efe8 100644
--- a/spec/features/projects/remote_mirror_spec.rb
+++ b/spec/features/projects/remote_mirror_spec.rb
@@ -17,7 +17,7 @@ describe 'Project remote mirror', :feature do
visit project_mirror_path(project)
- expect(page).to have_content('The remote repository failed to update.')
+ expect_mirror_to_have_error_and_timeago('Never')
end
end
@@ -27,8 +27,14 @@ describe 'Project remote mirror', :feature do
visit project_mirror_path(project)
- expect(page).to have_content('The remote repository failed to update 5 minutes ago.')
+ expect_mirror_to_have_error_and_timeago('5 minutes ago')
end
end
+
+ def expect_mirror_to_have_error_and_timeago(timeago)
+ row = first('.js-mirrors-table-body tr')
+ expect(row).to have_content('Error')
+ expect(row).to have_content(timeago)
+ end
end
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index a0f5b234ebc..377a75cbcb3 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -129,9 +129,8 @@ describe 'Projects > Settings > Repository settings' do
visit project_settings_repository_path(project)
end
- it 'shows push mirror settings' do
- expect(page).to have_selector('#project_remote_mirrors_attributes_0_enabled')
- expect(page).to have_selector('#project_remote_mirrors_attributes_0_url')
+ it 'shows push mirror settings', :js do
+ expect(page).to have_selector('#mirror_direction')
end
end
end
diff --git a/spec/finders/license_template_finder_spec.rb b/spec/finders/license_template_finder_spec.rb
new file mode 100644
index 00000000000..a97903103c9
--- /dev/null
+++ b/spec/finders/license_template_finder_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe LicenseTemplateFinder do
+ describe '#execute' do
+ subject(:result) { described_class.new(params).execute }
+
+ let(:categories) { categorised_licenses.keys }
+ let(:categorised_licenses) { result.group_by(&:category) }
+
+ context 'popular: true' do
+ let(:params) { { popular: true } }
+
+ it 'only returns popular licenses' do
+ expect(categories).to contain_exactly(:Popular)
+ expect(categorised_licenses[:Popular]).to be_present
+ end
+ end
+
+ context 'popular: false' do
+ let(:params) { { popular: false } }
+
+ it 'only returns unpopular licenses' do
+ expect(categories).to contain_exactly(:Other)
+ expect(categorised_licenses[:Other]).to be_present
+ end
+ end
+
+ context 'popular: nil' do
+ let(:params) { { popular: nil } }
+
+ it 'returns all licenses known by the Licensee gem' do
+ from_licensee = Licensee::License.all.map { |l| l.key }
+
+ expect(result.map(&:id)).to match_array(from_licensee)
+ end
+
+ it 'correctly copies all attributes' do
+ licensee = Licensee::License.all.first
+ found = result.find { |r| r.key == licensee.key }
+
+ aggregate_failures do
+ %i[key name content nickname url meta featured?].each do |k|
+ expect(found.public_send(k)).to eq(licensee.public_send(k))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 5856bccb5b8..55ee87163f9 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -5,12 +5,67 @@ describe AvatarsHelper do
let(:user) { create(:user) }
- describe '#project_icon' do
- it 'returns an url for the avatar' do
- project = create(:project, :public, avatar: File.open(uploaded_image_temp_path))
+ describe '#project_icon & #group_icon' do
+ shared_examples 'resource with a default avatar' do |source_type|
+ it 'returns a default avatar div' do
+ expect(public_send("#{source_type}_icon", *helper_args))
+ .to match(%r{<div class="identicon bg\d+">F</div>})
+ end
+ end
+
+ shared_examples 'resource with a custom avatar' do |source_type|
+ it 'returns a custom avatar image' do
+ expect(public_send("#{source_type}_icon", *helper_args))
+ .to eq "<img src=\"#{resource.avatar.url}\" alt=\"Banana sample\" />"
+ end
+ end
+
+ context 'when providing a project' do
+ it_behaves_like 'resource with a default avatar', 'project' do
+ let(:resource) { create(:project, name: 'foo') }
+ let(:helper_args) { [resource] }
+ end
+
+ it_behaves_like 'resource with a custom avatar', 'project' do
+ let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) }
+ let(:helper_args) { [resource] }
+ end
+ end
+
+ context 'when providing a project path' do
+ it_behaves_like 'resource with a default avatar', 'project' do
+ let(:resource) { create(:project, name: 'foo') }
+ let(:helper_args) { [resource.full_path] }
+ end
- expect(helper.project_icon(project.full_path).to_s)
- .to eq "<img data-src=\"#{project.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
+ it_behaves_like 'resource with a custom avatar', 'project' do
+ let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) }
+ let(:helper_args) { [resource.full_path] }
+ end
+ end
+
+ context 'when providing a group' do
+ it_behaves_like 'resource with a default avatar', 'group' do
+ let(:resource) { create(:group, name: 'foo') }
+ let(:helper_args) { [resource] }
+ end
+
+ it_behaves_like 'resource with a custom avatar', 'group' do
+ let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
+ let(:helper_args) { [resource] }
+ end
+ end
+
+ context 'when providing a group path' do
+ it_behaves_like 'resource with a default avatar', 'group' do
+ let(:resource) { create(:group, name: 'foo') }
+ let(:helper_args) { [resource.full_path] }
+ end
+
+ it_behaves_like 'resource with a custom avatar', 'group' do
+ let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
+ let(:helper_args) { [resource.full_path] }
+ end
end
end
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
index 630f3eff258..0c0a0003231 100644
--- a/spec/helpers/button_helper_spec.rb
+++ b/spec/helpers/button_helper_spec.rb
@@ -79,6 +79,18 @@ describe ButtonHelper do
end
end
+ context 'without an ssh key on the user and user_show_add_ssh_key_message unset' do
+ before do
+ stub_application_setting(user_show_add_ssh_key_message: false)
+ end
+
+ it 'there is no warning on the dropdown description' do
+ description = element.search('.dropdown-menu-inner-content').first
+
+ expect(description).to be_nil
+ end
+ end
+
context 'with an ssh key on the user' do
before do
create(:key, user: user)
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 115807f954b..540a8674ec2 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -3,19 +3,6 @@ require 'spec_helper'
describe GroupsHelper do
include ApplicationHelper
- describe 'group_icon' do
- it 'returns an url for the avatar' do
- avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif')
-
- group = create(:group)
- group.avatar = fixture_file_upload(avatar_file_path)
- group.save!
-
- expect(helper.group_icon(group).to_s)
- .to eq "<img data-src=\"#{group.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
- end
- end
-
describe 'group_icon_url' do
it 'returns an url for the avatar' do
avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif')
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index 82f588d1a08..4b40d523287 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -80,6 +80,26 @@ describe IconsHelper do
end
end
+ describe 'audit icon' do
+ it 'returns right icon name for standard auth' do
+ icon_name = 'standard'
+ expect(audit_icon(icon_name).to_s)
+ .to eq '<i class="fa fa-key"></i>'
+ end
+
+ it 'returns right icon name for two-factor auth' do
+ icon_name = 'two-factor'
+ expect(audit_icon(icon_name).to_s)
+ .to eq '<i class="fa fa-key"></i>'
+ end
+
+ it 'returns right icon name for google_oauth2 auth' do
+ icon_name = 'google_oauth2'
+ expect(audit_icon(icon_name).to_s)
+ .to eq '<i class="fa fa-google"></i>'
+ end
+ end
+
describe 'file_type_icon_class' do
it 'returns folder class' do
expect(file_type_icon_class('folder', 0, 'folder_name')).to eq 'folder'
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index 7a4616ec8eb..44a38f7ca82 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -22,11 +22,18 @@ describe('DiffFile', () => {
expect(el.id).toEqual(fileHash);
expect(el.classList.contains('diff-file')).toEqual(true);
+
expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0);
expect(el.querySelector('.js-file-title')).toBeDefined();
expect(el.querySelector('.file-title-name').innerText.indexOf(filePath) > -1).toEqual(true);
expect(el.querySelector('.js-syntax-highlight')).toBeDefined();
- expect(el.querySelectorAll('.line_content').length > 5).toEqual(true);
+
+ expect(vm.file.renderIt).toEqual(false);
+ vm.file.renderIt = true;
+
+ vm.$nextTick(() => {
+ expect(el.querySelectorAll('.line_content').length > 5).toEqual(true);
+ });
});
describe('collapsed', () => {
@@ -34,6 +41,7 @@ describe('DiffFile', () => {
expect(vm.$el.querySelectorAll('.diff-content').length).toEqual(1);
expect(vm.file.collapsed).toEqual(false);
vm.file.collapsed = true;
+ vm.file.renderIt = true;
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('.diff-content').length).toEqual(0);
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
index d3bf9525924..cce36ecc91f 100644
--- a/spec/javascripts/diffs/mock_data/diff_file.js
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -39,6 +39,7 @@ export default {
viewPath: '/gitlab-org/gitlab-test/blob/spooky-stuff/CHANGELOG',
replacedViewPath: null,
collapsed: false,
+ renderIt: false,
tooLarge: false,
contextLinesPath:
'/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff',
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 1af49f4985c..8f89984c6e5 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -1,6 +1,7 @@
import mutations from '~/diffs/store/mutations';
import * as types from '~/diffs/store/mutation_types';
import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
+import diffFileMockData from '../mock_data/diff_file';
describe('DiffsStoreMutations', () => {
describe('SET_BASE_CONFIG', () => {
@@ -24,6 +25,23 @@ describe('DiffsStoreMutations', () => {
});
});
+ describe('SET_DIFF_DATA', () => {
+ it('should set diff data type properly', () => {
+ const state = {};
+ const diffMock = {
+ diff_files: [diffFileMockData],
+ };
+
+ mutations[types.SET_DIFF_DATA](state, diffMock);
+
+ const firstLine = state.diffFiles[0].parallelDiffLines[0];
+
+ expect(firstLine.right.text).toBeUndefined();
+ expect(state.diffFiles[0].renderIt).toEqual(true);
+ expect(state.diffFiles[0].collapsed).toEqual(false);
+ });
+ });
+
describe('SET_DIFF_VIEW_TYPE', () => {
it('should set diff view type properly', () => {
const state = {};
diff --git a/spec/javascripts/ide/components/new_dropdown/button_spec.js b/spec/javascripts/ide/components/new_dropdown/button_spec.js
index ef083d06ba7..6a326b5bd92 100644
--- a/spec/javascripts/ide/components/new_dropdown/button_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/button_spec.js
@@ -46,4 +46,20 @@ describe('IDE new entry dropdown button component', () => {
done();
});
});
+
+ describe('tooltipTitle', () => {
+ it('returns empty string when showLabel is true', () => {
+ expect(vm.tooltipTitle).toBe('');
+ });
+
+ it('returns label', done => {
+ vm.showLabel = false;
+
+ vm.$nextTick(() => {
+ expect(vm.tooltipTitle).toBe('Testing');
+
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/preview/clientside_spec.js b/spec/javascripts/ide/components/preview/clientside_spec.js
index 3ec65882418..d6983f5a3b8 100644
--- a/spec/javascripts/ide/components/preview/clientside_spec.js
+++ b/spec/javascripts/ide/components/preview/clientside_spec.js
@@ -292,6 +292,8 @@ describe('IDE clientside preview', () => {
describe('update', () => {
beforeEach(() => {
jasmine.clock().install();
+
+ vm.sandpackReady = true;
vm.manager.updatePreview = jasmine.createSpy('updatePreview');
vm.manager.listener = jasmine.createSpy('updatePreview');
});
@@ -306,7 +308,7 @@ describe('IDE clientside preview', () => {
vm.update();
- jasmine.clock().tick(500);
+ jasmine.clock().tick(250);
expect(vm.initPreview).toHaveBeenCalled();
});
@@ -314,7 +316,7 @@ describe('IDE clientside preview', () => {
it('calls updatePreview', () => {
vm.update();
- jasmine.clock().tick(500);
+ jasmine.clock().tick(250);
expect(vm.manager.updatePreview).toHaveBeenCalledWith(vm.sandboxOpts);
});
diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js
index 90c28c769f7..8564f04ce8a 100644
--- a/spec/javascripts/ide/stores/actions/merge_request_spec.js
+++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js
@@ -1,12 +1,14 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
-import {
+import actions, {
getMergeRequestData,
getMergeRequestChanges,
getMergeRequestVersions,
+ openMergeRequest,
} from '~/ide/stores/actions/merge_request';
import service from '~/ide/services';
+import { activityBarViews } from '~/ide/constants';
import { resetStore } from '../../helpers';
describe('IDE store merge request actions', () => {
@@ -238,4 +240,101 @@ describe('IDE store merge request actions', () => {
});
});
});
+
+ describe('openMergeRequest', () => {
+ const mr = {
+ projectId: 'abcproject',
+ targetProjectId: 'defproject',
+ mergeRequestId: 2,
+ };
+ let testMergeRequest;
+ let testMergeRequestChanges;
+
+ beforeEach(() => {
+ testMergeRequest = {
+ source_branch: 'abcbranch',
+ };
+ testMergeRequestChanges = {
+ changes: [],
+ };
+ store.state.entries = {
+ foo: {},
+ bar: {},
+ };
+
+ spyOn(store, 'dispatch').and.callFake((type) => {
+ switch (type) {
+ case 'getMergeRequestData':
+ return Promise.resolve(testMergeRequest);
+ case 'getMergeRequestChanges':
+ return Promise.resolve(testMergeRequestChanges);
+ default:
+ return Promise.resolve();
+ }
+ });
+ });
+
+ it('dispatch actions for merge request data', done => {
+ openMergeRequest(store, mr)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['getMergeRequestData', mr],
+ ['setCurrentBranchId', testMergeRequest.source_branch],
+ ['getBranchData', {
+ projectId: mr.projectId,
+ branchId: testMergeRequest.source_branch,
+ }],
+ ['getFiles', {
+ projectId: mr.projectId,
+ branchId: testMergeRequest.source_branch,
+ }],
+ ['getMergeRequestVersions', mr],
+ ['getMergeRequestChanges', mr],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('updates activity bar view and gets file data, if changes are found', done => {
+ testMergeRequestChanges.changes = [
+ { new_path: 'foo' },
+ { new_path: 'bar' },
+ ];
+
+ openMergeRequest(store, mr)
+ .then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith('updateActivityBarView', activityBarViews.review);
+
+ testMergeRequestChanges.changes.forEach((change, i) => {
+ expect(store.dispatch).toHaveBeenCalledWith('setFileMrChange', {
+ file: store.state.entries[change.new_path],
+ mrChange: change,
+ });
+ expect(store.dispatch).toHaveBeenCalledWith('getFileData', {
+ path: change.new_path,
+ makeFileActive: i === 0,
+ });
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('flashes message, if error', done => {
+ const flashSpy = spyOnDependency(actions, 'flash');
+ store.dispatch.and.returnValue(Promise.reject());
+
+ openMergeRequest(store, mr)
+ .then(() => {
+ fail('Expected openMergeRequest to throw an error');
+ })
+ .catch(() => {
+ expect(flashSpy).toHaveBeenCalledWith(jasmine.any(String));
+ })
+ .then(done)
+ .catch(done.fail);
+
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index 6a85968e199..667e3e0a7ef 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -5,6 +5,7 @@ import {
showBranchNotFoundError,
createNewBranchFromDefault,
getBranchData,
+ openBranch,
} from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
@@ -224,4 +225,55 @@ describe('IDE store project actions', () => {
});
});
});
+
+ describe('openBranch', () => {
+ const branch = {
+ projectId: 'feature/lorem-ipsum',
+ branchId: '123-lorem',
+ };
+
+ beforeEach(() => {
+ store.state.entries = {
+ foo: { pending: false },
+ 'foo/bar-pending': { pending: true },
+ 'foo/bar': { pending: false },
+ };
+
+ spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
+ });
+
+ it('dispatches branch actions', done => {
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['setCurrentBranchId', branch.branchId],
+ ['getBranchData', branch],
+ ['getFiles', branch],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('handles tree entry action, if basePath is given', done => {
+ openBranch(store, { ...branch, basePath: 'foo/bar/' })
+ .then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ store.state.entries['foo/bar'],
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not handle tree entry action, if entry is pending', done => {
+ openBranch(store, { ...branch, basePath: 'foo/bar-pending' })
+ .then(() => {
+ expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything());
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/branches/actions_spec.js b/spec/javascripts/ide/stores/modules/branches/actions_spec.js
index a0fce578958..010f56af03b 100644
--- a/spec/javascripts/ide/stores/modules/branches/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/branches/actions_spec.js
@@ -9,7 +9,6 @@ import {
receiveBranchesSuccess,
fetchBranches,
resetBranches,
- openBranch,
} from '~/ide/stores/modules/branches/actions';
import { branches, projectData } from '../../../mock_data';
@@ -174,20 +173,5 @@ describe('IDE branches actions', () => {
);
});
});
-
- describe('openBranch', () => {
- it('dispatches goToRoute action with path', done => {
- const branchId = branches[0].name;
- const expectedPath = `/project/${projectData.name_with_namespace}/edit/${branchId}`;
- testAction(
- openBranch,
- branchId,
- mockedState,
- [],
- [{ type: 'goToRoute', payload: expectedPath }],
- done,
- );
- });
- });
});
});
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
index 1e836dbc3f9..6ce76aaa03b 100644
--- a/spec/javascripts/ide/stores/mutations_spec.js
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -213,6 +213,33 @@ describe('Multi-file store mutations', () => {
expect(localState.changedFiles).toEqual([localState.entries.filePath]);
});
+
+ it('does not add tempFile into changedFiles', () => {
+ localState.entries.filePath = {
+ deleted: false,
+ type: 'blob',
+ tempFile: true,
+ };
+
+ mutations.DELETE_ENTRY(localState, 'filePath');
+
+ expect(localState.changedFiles).toEqual([]);
+ });
+
+ it('removes tempFile from changedFiles when deleted', () => {
+ localState.entries.filePath = {
+ path: 'filePath',
+ deleted: false,
+ type: 'blob',
+ tempFile: true,
+ };
+
+ localState.changedFiles.push({ ...localState.entries.filePath });
+
+ mutations.DELETE_ENTRY(localState, 'filePath');
+
+ expect(localState.changedFiles).toEqual([]);
+ });
});
describe('UPDATE_FILE_AFTER_COMMIT', () => {
diff --git a/spec/javascripts/jobs/artifacts_block_spec.js b/spec/javascripts/jobs/artifacts_block_spec.js
new file mode 100644
index 00000000000..c544c6f3e89
--- /dev/null
+++ b/spec/javascripts/jobs/artifacts_block_spec.js
@@ -0,0 +1,120 @@
+import Vue from 'vue';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+import component from '~/jobs/components/artifacts_block.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Artifacts block', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ const expireAt = '2018-08-14T09:38:49.157Z';
+ const timeago = getTimeago();
+ const formatedDate = timeago.format(expireAt);
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('with expired artifacts', () => {
+ it('renders expired artifact date and info', () => {
+ vm = mountComponent(Component, {
+ haveArtifactsExpired: true,
+ willArtifactsExpire: false,
+ expireAt,
+ });
+
+ expect(vm.$el.querySelector('.js-artifacts-removed')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).toBeNull();
+ expect(vm.$el.textContent).toContain(formatedDate);
+ });
+ });
+
+ describe('with artifacts that will expire', () => {
+ it('renders will expire artifact date and info', () => {
+ vm = mountComponent(Component, {
+ haveArtifactsExpired: false,
+ willArtifactsExpire: true,
+ expireAt,
+ });
+
+ expect(vm.$el.querySelector('.js-artifacts-removed')).toBeNull();
+ expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).not.toBeNull();
+ expect(vm.$el.textContent).toContain(formatedDate);
+ });
+ });
+
+ describe('when the user can keep the artifacts', () => {
+ it('renders the keep button', () => {
+ vm = mountComponent(Component, {
+ haveArtifactsExpired: true,
+ willArtifactsExpire: false,
+ expireAt,
+ keepArtifactsPath: '/keep',
+ });
+
+ expect(vm.$el.querySelector('.js-keep-artifacts')).not.toBeNull();
+ });
+ });
+
+ describe('when the user can not keep the artifacts', () => {
+ it('does not render the keep button', () => {
+ vm = mountComponent(Component, {
+ haveArtifactsExpired: true,
+ willArtifactsExpire: false,
+ expireAt,
+ });
+
+ expect(vm.$el.querySelector('.js-keep-artifacts')).toBeNull();
+ });
+ });
+
+ describe('when the user can download the artifacts', () => {
+ it('renders the download button', () => {
+ vm = mountComponent(Component, {
+ haveArtifactsExpired: true,
+ willArtifactsExpire: false,
+ expireAt,
+ downloadArtifactsPath: '/download',
+ });
+
+ expect(vm.$el.querySelector('.js-download-artifacts')).not.toBeNull();
+ });
+ });
+
+ describe('when the user can not download the artifacts', () => {
+ it('does not render the keep button', () => {
+ vm = mountComponent(Component, {
+ haveArtifactsExpired: true,
+ willArtifactsExpire: false,
+ expireAt,
+ });
+
+ expect(vm.$el.querySelector('.js-download-artifacts')).toBeNull();
+ });
+ });
+
+ describe('when the user can browse the artifacts', () => {
+ it('does not render the browse button', () => {
+ vm = mountComponent(Component, {
+ haveArtifactsExpired: true,
+ willArtifactsExpire: false,
+ expireAt,
+ browseArtifactsPath: '/browse',
+ });
+
+ expect(vm.$el.querySelector('.js-browse-artifacts')).not.toBeNull();
+ });
+ });
+
+ describe('when the user can not browse the artifacts', () => {
+ it('does not render the browse button', () => {
+ vm = mountComponent(Component, {
+ haveArtifactsExpired: true,
+ willArtifactsExpire: false,
+ expireAt,
+ });
+
+ expect(vm.$el.querySelector('.js-browse-artifacts')).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/erased_block_spec.js b/spec/javascripts/jobs/erased_block_spec.js
new file mode 100644
index 00000000000..7cf32e984a2
--- /dev/null
+++ b/spec/javascripts/jobs/erased_block_spec.js
@@ -0,0 +1,56 @@
+import Vue from 'vue';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+import component from '~/jobs/components/erased_block.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Erased block', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ const erasedAt = '2016-11-07T11:11:16.525Z';
+ const timeago = getTimeago();
+ const formatedDate = timeago.format(erasedAt);
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('with job erased by user', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ erasedByUser: true,
+ username: 'root',
+ linkToUser: 'gitlab.com/root',
+ erasedAt,
+ });
+ });
+
+ it('renders username and link', () => {
+ expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('gitlab.com/root');
+
+ expect(vm.$el.textContent).toContain('Job has been erased by');
+ expect(vm.$el.textContent).toContain('root');
+ });
+
+ it('renders erasedAt', () => {
+ expect(vm.$el.textContent).toContain(formatedDate);
+ });
+ });
+
+ describe('with erased job', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ erasedByUser: false,
+ erasedAt,
+ });
+ });
+
+ it('renders username and link', () => {
+ expect(vm.$el.textContent).toContain('Job has been erased');
+ });
+
+ it('renders erasedAt', () => {
+ expect(vm.$el.textContent).toContain(formatedDate);
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/job_log_spec.js b/spec/javascripts/jobs/job_log_spec.js
new file mode 100644
index 00000000000..406f1c4ccc5
--- /dev/null
+++ b/spec/javascripts/jobs/job_log_spec.js
@@ -0,0 +1,45 @@
+import Vue from 'vue';
+import component from '~/jobs/components/job_log.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Job Log', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ const trace = 'Running with gitlab-runner 11.1.0 (081978aa)<br> on docker-auto-scale-com d5ae8d25<br>Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29 ...<br>';
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders provided trace', () => {
+ vm = mountComponent(Component, {
+ trace,
+ isReceivingBuildTrace: true,
+ });
+
+ expect(vm.$el.querySelector('code').textContent).toContain('Running with gitlab-runner 11.1.0 (081978aa)');
+ });
+
+ describe('while receiving trace', () => {
+ it('renders animation', () => {
+ vm = mountComponent(Component, {
+ trace,
+ isReceivingBuildTrace: true,
+ });
+
+ expect(vm.$el.querySelector('.js-log-animation')).not.toBeNull();
+ });
+ });
+
+ describe('when build trace has finishes', () => {
+ it('does not render animation', () => {
+ vm = mountComponent(Component, {
+ trace,
+ isReceivingBuildTrace: false,
+ });
+
+ expect(vm.$el.querySelector('.js-log-animation')).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/sidebar_details_block_spec.js b/spec/javascripts/jobs/sidebar_details_block_spec.js
index 9c4454252ce..21ef5650b80 100644
--- a/spec/javascripts/jobs/sidebar_details_block_spec.js
+++ b/spec/javascripts/jobs/sidebar_details_block_spec.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import sidebarDetailsBlock from '~/jobs/components/sidebar_details_block.vue';
import job from './mock_data';
+import mountComponent from '../helpers/vue_mount_component_helper';
describe('Sidebar details block', () => {
let SidebarComponent;
@@ -20,39 +21,53 @@ describe('Sidebar details block', () => {
describe('when it is loading', () => {
it('should render a loading spinner', () => {
- vm = new SidebarComponent({
- propsData: {
- job: {},
- isLoading: true,
- },
- }).$mount();
-
+ vm = mountComponent(SidebarComponent, {
+ job: {},
+ isLoading: true,
+ });
expect(vm.$el.querySelector('.fa-spinner')).toBeDefined();
});
});
- describe("when user can't retry", () => {
+ describe('when there is no retry path retry', () => {
it('should not render a retry button', () => {
- vm = new SidebarComponent({
- propsData: {
- job: {},
- canUserRetry: false,
- isLoading: true,
- },
- }).$mount();
+ vm = mountComponent(SidebarComponent, {
+ job: {},
+ isLoading: false,
+ });
expect(vm.$el.querySelector('.js-retry-job')).toBeNull();
});
});
- beforeEach(() => {
- vm = new SidebarComponent({
- propsData: {
+ describe('without terminal path', () => {
+ it('does not render terminal link', () => {
+ vm = mountComponent(SidebarComponent, {
job,
- canUserRetry: true,
isLoading: false,
- },
- }).$mount();
+ });
+
+ expect(vm.$el.querySelector('.js-terminal-link')).toBeNull();
+ });
+ });
+
+ describe('with terminal path', () => {
+ it('renders terminal link', () => {
+ vm = mountComponent(SidebarComponent, {
+ job,
+ isLoading: false,
+ terminalPath: 'job/43123/terminal',
+ });
+
+ expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull();
+ });
+ });
+
+ beforeEach(() => {
+ vm = mountComponent(SidebarComponent, {
+ job,
+ isLoading: false,
+ });
});
describe('actions', () => {
@@ -102,13 +117,15 @@ describe('Sidebar details block', () => {
});
it('should render runner ID', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual('Runner: local ci runner (#1)');
+ expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual(
+ 'Runner: local ci runner (#1)',
+ );
});
it('should render timeout information', () => {
- expect(
- trimWhitespace(vm.$el.querySelector('.js-job-timeout')),
- ).toEqual('Timeout: 1m 40s (from runner)');
+ expect(trimWhitespace(vm.$el.querySelector('.js-job-timeout'))).toEqual(
+ 'Timeout: 1m 40s (from runner)',
+ );
});
it('should render coverage', () => {
diff --git a/spec/javascripts/jobs/stuck_block_spec.js b/spec/javascripts/jobs/stuck_block_spec.js
new file mode 100644
index 00000000000..4e2108dfdfb
--- /dev/null
+++ b/spec/javascripts/jobs/stuck_block_spec.js
@@ -0,0 +1,81 @@
+import Vue from 'vue';
+import component from '~/jobs/components/stuck_block.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Stuck Block Job component', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('with no runners for project', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ hasNoRunnersForProject: true,
+ runnersPath: '/root/project/runners#js-runners-settings',
+ });
+ });
+
+ it('renders only information about project not having runners', () => {
+ expect(vm.$el.querySelector('.js-stuck-no-runners')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull();
+ });
+
+ it('renders link to runners page', () => {
+ expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual(
+ '/root/project/runners#js-runners-settings',
+ );
+ });
+ });
+
+ describe('with tags', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ hasNoRunnersForProject: false,
+ tags: ['docker', 'gitlab-org'],
+ runnersPath: '/root/project/runners#js-runners-settings',
+ });
+ });
+
+ it('renders information about the tags not being set', () => {
+ expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-with-tags')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull();
+ });
+
+ it('renders tags', () => {
+ expect(vm.$el.textContent).toContain('docker');
+ expect(vm.$el.textContent).toContain('gitlab-org');
+ });
+
+ it('renders link to runners page', () => {
+ expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual(
+ '/root/project/runners#js-runners-settings',
+ );
+ });
+ });
+
+ describe('without active runners', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ hasNoRunnersForProject: false,
+ runnersPath: '/root/project/runners#js-runners-settings',
+ });
+ });
+
+ it('renders information about project not having runners', () => {
+ expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-no-active-runner')).not.toBeNull();
+ });
+
+ it('renders link to runners page', () => {
+ expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual(
+ '/root/project/runners#js-runners-settings',
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js b/spec/javascripts/reports/components/modal_open_name_spec.js
index 8635203c413..b18b3ef03d1 100644
--- a/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js
+++ b/spec/javascripts/reports/components/modal_open_name_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import Vuex from 'vuex';
-import component from '~/vue_shared/components/reports/modal_open_name.vue';
+import component from '~/reports/components/modal_open_name.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
describe('Modal open name', () => {
diff --git a/spec/javascripts/vue_shared/components/reports/report_link_spec.js b/spec/javascripts/reports/components/report_link_spec.js
index a4691f3712f..cd6911e2f59 100644
--- a/spec/javascripts/vue_shared/components/reports/report_link_spec.js
+++ b/spec/javascripts/reports/components/report_link_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import component from '~/vue_shared/components/reports/report_link.vue';
-import mountComponent from '../../../helpers/vue_mount_component_helper';
+import component from '~/reports/components/report_link.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('report link', () => {
let vm;
diff --git a/spec/javascripts/vue_shared/components/reports/report_section_spec.js b/spec/javascripts/reports/components/report_section_spec.js
index 4e3986acb16..6f6eb161d14 100644
--- a/spec/javascripts/vue_shared/components/reports/report_section_spec.js
+++ b/spec/javascripts/reports/components/report_section_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import reportSection from '~/vue_shared/components/reports/report_section.vue';
+import reportSection from '~/reports/components/report_section.vue';
import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper';
describe('Report section', () => {
diff --git a/spec/javascripts/vue_shared/components/reports/summary_row_spec.js b/spec/javascripts/reports/components/summary_row_spec.js
index ac076f05bc0..fab7693581c 100644
--- a/spec/javascripts/vue_shared/components/reports/summary_row_spec.js
+++ b/spec/javascripts/reports/components/summary_row_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import component from '~/vue_shared/components/reports/summary_row.vue';
+import component from '~/reports/components/summary_row.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Summary row', () => {
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
new file mode 100644
index 00000000000..48140305e26
--- /dev/null
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::ProjectReferenceFilter do
+ include FilterSpecHelper
+
+ def invalidate_reference(reference)
+ "#{reference.reverse}"
+ end
+
+ def get_reference(project)
+ project.to_reference_with_postfix
+ end
+
+ let(:project) { create(:project, :public) }
+ subject { project }
+ let(:subject_name) { "project" }
+ let(:reference) { get_reference(project) }
+
+ it_behaves_like 'user reference or project reference'
+
+ it 'ignores invalid projects' do
+ exp = act = "Hey #{invalidate_reference(reference)}"
+
+ expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp))
+ end
+
+ it 'allows references with text after the > character' do
+ doc = reference_filter("Hey #{reference}foo")
+ expect(doc.css('a').first.attr('href')).to eq urls.project_url(subject)
+ end
+
+ %w(pre code a style).each do |elem|
+ it "ignores valid references contained inside '#{elem}' element" do
+ exp = act = "<#{elem}>Hey #{CGI.escapeHTML(reference)}</#{elem}>"
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project has-tooltip'
+ end
+
+ context 'in group context' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ let(:nested_group) { create(:group, :nested) }
+ let(:nested_project) { create(:project, group: nested_group) }
+
+ it 'supports mentioning a project' do
+ reference = get_reference(project)
+ doc = reference_filter("Hey #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.project_url(project)
+ end
+
+ it 'supports mentioning a project in a nested group' do
+ reference = get_reference(nested_project)
+ doc = reference_filter("Hey #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.project_url(nested_project)
+ end
+ end
+
+ describe '#projects_hash' do
+ it 'returns a Hash containing all Projects' do
+ document = Nokogiri::HTML.fragment("<p>#{get_reference(project)}</p>")
+ filter = described_class.new(document, project: project)
+
+ expect(filter.projects_hash).to eq({ project.full_path => project })
+ end
+ end
+
+ describe '#projects' do
+ it 'returns the projects mentioned in a document' do
+ document = Nokogiri::HTML.fragment("<p>#{get_reference(project)}</p>")
+ filter = described_class.new(document, project: project)
+
+ expect(filter.projects).to eq([project.full_path])
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 2f86a046d28..334d29a5368 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -3,9 +3,17 @@ require 'spec_helper'
describe Banzai::Filter::UserReferenceFilter do
include FilterSpecHelper
+ def get_reference(user)
+ user.to_reference
+ end
+
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
- let(:reference) { user.to_reference }
+ subject { user }
+ let(:subject_name) { "user" }
+ let(:reference) { get_reference(user) }
+
+ it_behaves_like 'user reference or project reference'
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
@@ -66,45 +74,6 @@ describe Banzai::Filter::UserReferenceFilter do
end
end
- context 'mentioning a user' do
- it_behaves_like 'a reference containing an element node'
-
- it 'links to a User' do
- doc = reference_filter("Hey #{reference}")
- expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
- end
-
- it 'links to a User with a period' do
- user = create(:user, name: 'alphA.Beta')
-
- doc = reference_filter("Hey #{user.to_reference}")
- expect(doc.css('a').length).to eq 1
- end
-
- it 'links to a User with an underscore' do
- user = create(:user, name: 'ping_pong_king')
-
- doc = reference_filter("Hey #{user.to_reference}")
- expect(doc.css('a').length).to eq 1
- end
-
- it 'links to a User with different case-sensitivity' do
- user = create(:user, username: 'RescueRanger')
-
- doc = reference_filter("Hey #{user.to_reference.upcase}")
- expect(doc.css('a').length).to eq 1
- expect(doc.css('a').text).to eq(user.to_reference)
- end
-
- it 'includes a data-user attribute' do
- doc = reference_filter("Hey #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-user')
- expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
- end
- end
-
context 'mentioning a group' do
it_behaves_like 'a reference containing an element node'
@@ -154,36 +123,6 @@ describe Banzai::Filter::UserReferenceFilter do
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip'
end
- it 'supports an :only_path context' do
- doc = reference_filter("Hey #{reference}", only_path: true)
- link = doc.css('a').first.attr('href')
-
- expect(link).not_to match %r(https?://)
- expect(link).to eq urls.user_path(user)
- end
-
- context 'referencing a user in a link href' do
- let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} }
-
- it 'links to a User' do
- doc = reference_filter("Hey #{reference}")
- expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
- end
-
- it 'links with adjacent text' do
- doc = reference_filter("Mention me (#{reference}.)")
- expect(doc.to_html).to match(%r{\(<a.+>User</a>\.\)})
- end
-
- it 'includes a data-user attribute' do
- doc = reference_filter("Hey #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-user')
- expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
- end
- end
-
context 'when a project is not specified' do
let(:project) { nil }
@@ -227,7 +166,7 @@ describe Banzai::Filter::UserReferenceFilter do
end
it 'supports mentioning a single user' do
- reference = group_member.to_reference
+ reference = get_reference(group_member)
doc = reference_filter("Hey #{reference}", context)
expect(doc.css('a').first.attr('href')).to eq urls.user_url(group_member)
@@ -243,7 +182,7 @@ describe Banzai::Filter::UserReferenceFilter do
describe '#namespaces' do
it 'returns a Hash containing all Namespaces' do
- document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
+ document = Nokogiri::HTML.fragment("<p>#{get_reference(user)}</p>")
filter = described_class.new(document, project: project)
ns = user.namespace
@@ -253,7 +192,7 @@ describe Banzai::Filter::UserReferenceFilter do
describe '#usernames' do
it 'returns the usernames mentioned in a document' do
- document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
+ document = Nokogiri::HTML.fragment("<p>#{get_reference(user)}</p>")
filter = described_class.new(document, project: project)
expect(filter.usernames).to eq([user.username])
diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb
new file mode 100644
index 00000000000..e4936aa9e57
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::ProjectParser do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:project, :public) }
+ let(:user) { create(:user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ describe 'when the link has a data-project attribute' do
+ context 'using an existing project ID' do
+ it 'returns an Array of projects' do
+ link['data-project'] = project.id.to_s
+
+ expect(subject.gather_references([link])).to eq([project])
+ end
+ end
+
+ context 'using a non-existing project ID' do
+ it 'returns an empty Array' do
+ link['data-project'] = ''
+
+ expect(subject.gather_references([link])).to eq([])
+ end
+ end
+
+ context 'using a private project ID' do
+ it 'returns an empty Array when unauthorized' do
+ private_project = create(:project, :private)
+
+ link['data-project'] = private_project.id.to_s
+
+ expect(subject.gather_references([link])).to eq([])
+ end
+
+ it 'returns an Array when authorized' do
+ private_project = create(:project, :private, namespace: user.namespace)
+
+ link['data-project'] = private_project.id.to_s
+
+ expect(subject.gather_references([link])).to eq([private_project])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
index 37b38776775..11e605eece6 100644
--- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
@@ -244,9 +244,11 @@ describe Gitlab::Cleanup::ProjectUploads do
orphaned1 = create(:upload, :personal_snippet_upload, :with_file)
orphaned2 = create(:upload, :namespace_upload, :with_file)
orphaned3 = create(:upload, :attachment_upload, :with_file)
+ orphaned4 = create(:upload, :favicon_upload, :with_file)
paths << orphaned1.absolute_path
paths << orphaned2.absolute_path
paths << orphaned3.absolute_path
+ paths << orphaned4.absolute_path
Upload.delete_all
expect(logger).not_to receive(:info).with(/move|fix/i)
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index eb7148ff108..4e83b27e4a5 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -48,10 +48,10 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:transaction_open?).and_return(false)
end
- context 'using PostgreSQL' do
+ context 'using PostgreSQL', :postgresql do
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- allow(model).to receive(:disable_statement_timeout)
+ allow(model).to receive(:disable_statement_timeout).and_call_original
end
it 'creates the index concurrently' do
@@ -114,12 +114,12 @@ describe Gitlab::Database::MigrationHelpers do
before do
allow(model).to receive(:transaction_open?).and_return(false)
allow(model).to receive(:index_exists?).and_return(true)
+ allow(model).to receive(:disable_statement_timeout).and_call_original
end
context 'using PostgreSQL' do
before do
allow(model).to receive(:supports_drop_index_concurrently?).and_return(true)
- allow(model).to receive(:disable_statement_timeout)
end
describe 'by column name' do
@@ -162,7 +162,7 @@ describe Gitlab::Database::MigrationHelpers do
context 'using MySQL' do
it 'removes an index' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(false).twice
expect(model).to receive(:remove_index)
.with(:users, { column: :foo })
@@ -224,21 +224,26 @@ describe Gitlab::Database::MigrationHelpers do
context 'using PostgreSQL' do
before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
allow(Gitlab::Database).to receive(:mysql?).and_return(false)
end
it 'creates a concurrent foreign key and validates it' do
- expect(model).to receive(:disable_statement_timeout)
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).with(/RESET ALL/)
model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
end
it 'appends a valid ON DELETE statement' do
- expect(model).to receive(:disable_statement_timeout)
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).with(/RESET ALL/)
model.add_concurrent_foreign_key(:projects, :users,
column: :user_id,
@@ -291,13 +296,68 @@ describe Gitlab::Database::MigrationHelpers do
describe '#disable_statement_timeout' do
context 'using PostgreSQL' do
- it 'disables statement timeouts' do
+ it 'disables statement timeouts to current transaction only' do
expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
- expect(model).to receive(:execute).with('SET statement_timeout TO 0')
+ expect(model).to receive(:execute).with('SET LOCAL statement_timeout TO 0')
model.disable_statement_timeout
end
+
+ # this specs runs without an enclosing transaction (:delete truncation method for db_cleaner)
+ context 'with real environment', :postgresql, :delete do
+ before do
+ model.execute("SET statement_timeout TO '20000'")
+ end
+
+ after do
+ model.execute('RESET ALL')
+ end
+
+ it 'defines statement to 0 only for current transaction' do
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
+
+ model.connection.transaction do
+ model.disable_statement_timeout
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0')
+ end
+
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
+ end
+ end
+
+ context 'when passing a blocks' do
+ it 'disables statement timeouts on session level and executes the block' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ expect(model).to receive(:execute).with('SET statement_timeout TO 0')
+ expect(model).to receive(:execute).with('RESET ALL')
+
+ expect { |block| model.disable_statement_timeout(&block) }.to yield_control
+ end
+
+ # this specs runs without an enclosing transaction (:delete truncation method for db_cleaner)
+ context 'with real environment', :postgresql, :delete do
+ before do
+ model.execute("SET statement_timeout TO '20000'")
+ end
+
+ after do
+ model.execute('RESET ALL')
+ end
+
+ it 'defines statement to 0 for any code run inside the block' do
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
+
+ model.disable_statement_timeout do
+ model.connection.transaction do
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0')
+ end
+
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0')
+ end
+ end
+ end
+ end
end
context 'using MySQL' do
@@ -308,6 +368,16 @@ describe Gitlab::Database::MigrationHelpers do
model.disable_statement_timeout
end
+
+ context 'when passing a blocks' do
+ it 'executes the block of code' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
+ expect(model).not_to receive(:execute)
+
+ expect { |block| model.disable_statement_timeout(&block) }.to yield_control
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb
new file mode 100644
index 00000000000..2f4e043a20f
--- /dev/null
+++ b/spec/lib/gitlab/git/merge_base_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Git::MergeBase do
+ set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ subject(:merge_base) { described_class.new(repository, refs) }
+
+ shared_context 'existing refs with a merge base', :existing_refs do
+ let(:refs) do
+ %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209)
+ end
+ end
+
+ shared_context 'when passing a missing ref', :missing_ref do
+ let(:refs) do
+ %w(304d257dcb821665ab5110318fc58a007bd104ed aaaa)
+ end
+ end
+
+ shared_context 'when passing refs that do not have a common ancestor', :no_common_ancestor do
+ let(:refs) { ['304d257dcb821665ab5110318fc58a007bd104ed', TestEnv::BRANCH_SHA['orphaned-branch']] }
+ end
+
+ describe '#sha' do
+ context 'when the refs exist', :existing_refs do
+ it 'returns the SHA of the merge base' do
+ expect(merge_base.sha).not_to be_nil
+ end
+
+ it 'memoizes the result' do
+ expect(repository).to receive(:merge_base).once.and_call_original
+
+ 2.times { merge_base.sha }
+ end
+ end
+
+ context 'when passing a missing ref', :missing_ref do
+ it 'does not call merge_base on the repository but raises an error' do
+ expect(repository).not_to receive(:merge_base)
+
+ expect { merge_base.sha }.to raise_error(Gitlab::Git::UnknownRef)
+ end
+ end
+
+ it 'returns `nil` when the refs do not have a common ancestor', :no_common_ancestor do
+ expect(merge_base.sha).to be_nil
+ end
+
+ it 'returns a merge base when passing 2 branch names' do
+ merge_base = described_class.new(repository, %w(master feature))
+
+ expect(merge_base.sha).to be_present
+ end
+
+ it 'returns a merge base when passing a tag name' do
+ merge_base = described_class.new(repository, %w(master v1.0.0))
+
+ expect(merge_base.sha).to be_present
+ end
+ end
+
+ describe '#commit' do
+ context 'for existing refs with a merge base', :existing_refs do
+ it 'finds the commit for the merge base' do
+ expect(merge_base.commit).to be_a(Commit)
+ end
+
+ it 'only looks up the commit once' do
+ expect(repository).to receive(:commit_by).once.and_call_original
+
+ 2.times { merge_base.commit }
+ end
+ end
+
+ it 'does not try to find the commit when there is no sha', :no_common_ancestor do
+ expect(repository).not_to receive(:commit_by)
+
+ merge_base.commit
+ end
+ end
+
+ describe '#unknown_refs', :missing_ref do
+ it 'returns the the refs passed that are not part of the repository' do
+ expect(merge_base.unknown_refs).to contain_exactly('aaaa')
+ end
+
+ it 'only looks up the commits once' do
+ expect(merge_base).to receive(:commits_for_refs).once.and_call_original
+
+ 2.times { merge_base.unknown_refs }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
new file mode 100644
index 00000000000..2eabccd5dff
--- /dev/null
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Template::Finders::RepoTemplateFinder do
+ set(:project) { create(:project, :repository) }
+
+ let(:categories) { { 'HTML' => 'html' } }
+
+ subject(:finder) { described_class.new(project, 'files/', '.html', categories) }
+
+ describe '#read' do
+ it 'returns the content of the given path' do
+ result = finder.read('files/html/500.html')
+
+ expect(result).to be_present
+ end
+
+ it 'raises an error if the path does not exist' do
+ expect { finder.read('does/not/exist') }.to raise_error(described_class::FileNotFoundError)
+ end
+ end
+
+ describe '#find' do
+ it 'returns the full path of the found template' do
+ result = finder.find('500')
+
+ expect(result).to eq('files/html/500.html')
+ end
+ end
+
+ describe '#list_files_for' do
+ it 'returns the full path of the found files' do
+ result = finder.list_files_for('files/html')
+
+ expect(result).to contain_exactly('files/html/500.html')
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 32b8755ee9a..42b627b6823 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1103,6 +1103,12 @@ describe Ci::Build do
it { is_expected.to be_cancelable }
end
+
+ context 'when build is created' do
+ let(:build) { create(:ci_build, :created) }
+
+ it { is_expected.to be_cancelable }
+ end
end
context 'when build is not cancelable' do
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
index 20600f5fa38..f2aad455d5f 100644
--- a/spec/models/internal_id_spec.rb
+++ b/spec/models/internal_id_spec.rb
@@ -30,7 +30,7 @@ describe InternalId do
context 'with existing issues' do
before do
- rand(1..10).times { create(:issue, project: project) }
+ create_list(:issue, 2, project: project)
described_class.delete_all
end
@@ -54,7 +54,7 @@ describe InternalId do
end
it 'generates a strictly monotone, gapless sequence' do
- seq = (0..rand(100)).map do
+ seq = Array.new(10).map do
described_class.generate_next(issue, scope, usage, init)
end
normalized = seq.map { |i| i - seq.min }
diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb
new file mode 100644
index 00000000000..c633e1908d4
--- /dev/null
+++ b/spec/models/license_template_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe LicenseTemplate do
+ describe '#content' do
+ it 'calls a proc exactly once if provided' do
+ lazy = build_template(-> { 'bar' })
+ content = lazy.content
+
+ expect(content).to eq('bar')
+ expect(content.object_id).to eq(lazy.content.object_id)
+
+ content.replace('foo')
+ expect(lazy.content).to eq('foo')
+ end
+
+ it 'returns a string if provided' do
+ lazy = build_template('bar')
+
+ expect(lazy.content).to eq('bar')
+ end
+ end
+
+ describe '#resolve!' do
+ let(:content) do
+ <<~TEXT
+ Pretend License
+
+ [project]
+
+ Copyright (c) [year] [fullname]
+ TEXT
+ end
+
+ let(:expected) do
+ <<~TEXT
+ Pretend License
+
+ Foo Project
+
+ Copyright (c) 1985 Nick Thomas
+ TEXT
+ end
+
+ let(:template) { build_template(content) }
+
+ it 'updates placeholders in a copy of the template content' do
+ expect(template.content.object_id).to eq(content.object_id)
+
+ template.resolve!(project_name: "Foo Project", fullname: "Nick Thomas", year: "1985")
+
+ expect(template.content).to eq(expected)
+ expect(template.content.object_id).not_to eq(content.object_id)
+ end
+ end
+
+ def build_template(content)
+ described_class.new(id: 'foo', name: 'foo', category: :Other, content: content)
+ end
+end
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index 77c475b9f52..e545b674b4f 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -94,9 +94,39 @@ RSpec.describe NotificationSetting do
end
end
- context 'email events' do
- it 'includes EXCLUDED_WATCHER_EVENTS in EMAIL_EVENTS' do
- expect(described_class::EMAIL_EVENTS).to include(*described_class::EXCLUDED_WATCHER_EVENTS)
+ describe '.email_events' do
+ subject { described_class.email_events }
+
+ it 'returns email events' do
+ expect(subject).to include(
+ :new_note,
+ :new_issue,
+ :reopen_issue,
+ :close_issue,
+ :reassign_issue,
+ :new_merge_request,
+ :reopen_merge_request,
+ :close_merge_request,
+ :reassign_merge_request,
+ :merge_merge_request,
+ :failed_pipeline,
+ :success_pipeline
+ )
+ end
+
+ it 'includes EXCLUDED_WATCHER_EVENTS' do
+ expect(subject).to include(*described_class::EXCLUDED_WATCHER_EVENTS)
+ end
+ end
+
+ describe '#email_events' do
+ let(:source) { build(:group) }
+
+ subject { build(:notification_setting, source: source) }
+
+ it 'calls email_events' do
+ expect(described_class).to receive(:email_events).with(source)
+ subject.email_events
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 03beb9187ed..076de06cf99 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -364,6 +364,15 @@ describe Project do
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
end
+ describe '#to_reference_with_postfix' do
+ it 'returns the full path with reference_postfix' do
+ namespace = create(:namespace, path: 'sample-namespace')
+ project = create(:project, path: 'sample-project', namespace: namespace)
+
+ expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>'
+ end
+ end
+
describe '#to_reference' do
let(:owner) { create(:user, name: 'Gitlab') }
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 52ec8dbe25a..2859d5149ec 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -296,41 +296,31 @@ describe Repository do
end
describe '#new_commits' do
- shared_examples 'finding unreferenced commits' do
- set(:project) { create(:project, :repository) }
- let(:repository) { project.repository }
+ set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
- subject { repository.new_commits(rev) }
+ subject { repository.new_commits(rev) }
- context 'when there are no new commits' do
- let(:rev) { repository.commit.id }
+ context 'when there are no new commits' do
+ let(:rev) { repository.commit.id }
- it 'returns an empty array' do
- expect(subject).to eq([])
- end
+ it 'returns an empty array' do
+ expect(subject).to eq([])
end
+ end
- context 'when new commits are found' do
- let(:branch) { 'orphaned-branch' }
- let!(:rev) { repository.commit(branch).id }
+ context 'when new commits are found' do
+ let(:branch) { 'orphaned-branch' }
+ let!(:rev) { repository.commit(branch).id }
- it 'returns the commits' do
- repository.delete_branch(branch)
+ it 'returns the commits' do
+ repository.delete_branch(branch)
- expect(subject).not_to be_empty
- expect(subject).to all( be_a(::Commit) )
- expect(subject.size).to eq(1)
- end
+ expect(subject).not_to be_empty
+ expect(subject).to all( be_a(::Commit) )
+ expect(subject.size).to eq(1)
end
end
-
- context 'when Gitaly handles the request' do
- it_behaves_like 'finding unreferenced commits'
- end
-
- context 'when Gitaly is disabled', :disable_gitaly do
- it_behaves_like 'finding unreferenced commits'
- end
end
describe '#commits_by' do
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 5814d834572..6adbbb40489 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -3,6 +3,32 @@ require 'spec_helper'
describe API::Jobs do
include HttpIOHelpers
+ shared_examples 'a job with artifacts and trace' do |result_is_array: true|
+ context 'with artifacts and trace' do
+ let!(:second_job) { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) }
+
+ it 'returns artifacts and trace data', :skip_before_request do
+ get api(api_endpoint, api_user)
+ json_job = result_is_array ? json_response.select { |job| job['id'] == second_job.id }.first : json_response
+
+ expect(json_job['artifacts_file']).not_to be_nil
+ expect(json_job['artifacts_file']).not_to be_empty
+ expect(json_job['artifacts_file']['filename']).to eq(second_job.artifacts_file.filename)
+ expect(json_job['artifacts_file']['size']).to eq(second_job.artifacts_file.size)
+ expect(json_job['artifacts']).not_to be_nil
+ expect(json_job['artifacts']).to be_an Array
+ expect(json_job['artifacts'].size).to eq(second_job.job_artifacts.length)
+ json_job['artifacts'].each do |artifact|
+ expect(artifact).not_to be_nil
+ file_type = Ci::JobArtifact.file_types[artifact['file_type']]
+ expect(artifact['size']).to eq(second_job.job_artifacts.where(file_type: file_type).first.size)
+ expect(artifact['filename']).to eq(second_job.job_artifacts.where(file_type: file_type).first.filename)
+ expect(artifact['file_format']).to eq(second_job.job_artifacts.where(file_type: file_type).first.file_format)
+ end
+ end
+ end
+ end
+
set(:project) do
create(:project, :repository, public_builds: false)
end
@@ -49,6 +75,20 @@ describe API::Jobs do
expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at)
end
+ context 'without artifacts and trace' do
+ it 'returns no artifacts nor trace data' do
+ json_job = json_response.first
+
+ expect(json_job['artifacts_file']).to be_nil
+ expect(json_job['artifacts']).to be_an Array
+ expect(json_job['artifacts']).to be_empty
+ end
+ end
+
+ it_behaves_like 'a job with artifacts and trace' do
+ let(:api_endpoint) { "/projects/#{project.id}/jobs" }
+ end
+
it 'returns pipeline data' do
json_job = json_response.first
@@ -60,7 +100,7 @@ describe API::Jobs do
end
it 'avoids N+1 queries', :skip_before_request do
- first_build = create(:ci_build, :artifacts, pipeline: pipeline)
+ first_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline)
first_build.runner = create(:ci_runner)
first_build.user = create(:user)
first_build.save
@@ -68,7 +108,7 @@ describe API::Jobs do
control_count = ActiveRecord::QueryRecorder.new { go }.count
second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
- second_build = create(:ci_build, :artifacts, pipeline: second_pipeline)
+ second_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: second_pipeline)
second_build.runner = create(:ci_runner)
second_build.user = create(:user)
second_build.save
@@ -117,9 +157,11 @@ describe API::Jobs do
describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do
let(:query) { Hash.new }
- before do
- job
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
+ before do |example|
+ unless example.metadata[:skip_before_request]
+ job
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
+ end
end
context 'authorized user' do
@@ -133,6 +175,13 @@ describe API::Jobs do
expect(json_response).not_to be_empty
expect(json_response.first['commit']['id']).to eq project.commit.id
expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at)
+ expect(json_response.first['artifacts_file']).to be_nil
+ expect(json_response.first['artifacts']).to be_an Array
+ expect(json_response.first['artifacts']).to be_empty
+ end
+
+ it_behaves_like 'a job with artifacts and trace' do
+ let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" }
end
it 'returns pipeline data' do
@@ -183,7 +232,7 @@ describe API::Jobs do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
end.count
- 3.times { create(:ci_build, :artifacts, pipeline: pipeline) }
+ 3.times { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) }
expect do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
@@ -201,8 +250,10 @@ describe API::Jobs do
end
describe 'GET /projects/:id/jobs/:job_id' do
- before do
- get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
+ before do |example|
+ unless example.metadata[:skip_before_request]
+ get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
+ end
end
context 'authorized user' do
@@ -219,10 +270,17 @@ describe API::Jobs do
expect(Time.parse(json_response['started_at'])).to be_like_time(job.started_at)
expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at)
expect(Time.parse(json_response['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at)
+ expect(json_response['artifacts_file']).to be_nil
+ expect(json_response['artifacts']).to be_an Array
+ expect(json_response['artifacts']).to be_empty
expect(json_response['duration']).to eq(job.duration)
expect(json_response['web_url']).to be_present
end
+ it_behaves_like 'a job with artifacts and trace', result_is_array: false do
+ let(:api_endpoint) { "/projects/#{project.id}/jobs/#{second_job.id}" }
+ end
+
it 'returns pipeline data' do
json_job = json_response
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 6063afc213d..519638ebb82 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -465,4 +465,77 @@ describe API::Repositories do
end
end
end
+
+ describe 'GET :id/repository/merge_base' do
+ let(:refs) do
+ %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209)
+ end
+
+ subject(:request) do
+ get(api("/projects/#{project.id}/repository/merge_base", current_user), refs: refs)
+ end
+
+ shared_examples 'merge base' do
+ it 'returns the common ancestor' do
+ request
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response['id']).to be_present
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'merge base' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:current_user) { nil }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'merge base' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:current_user) { guest }
+ end
+ end
+
+ context 'when passing refs that do not exist' do
+ it_behaves_like '400 response' do
+ let(:refs) { %w(304d257dcb821665ab5110318fc58a007bd104ed missing) }
+ let(:current_user) { user }
+ let(:message) { 'Could not find ref: missing' }
+ end
+ end
+
+ context 'when passing refs that do not have a merge base' do
+ it_behaves_like '404 response' do
+ let(:refs) { ['304d257dcb821665ab5110318fc58a007bd104ed', TestEnv::BRANCH_SHA['orphaned-branch']] }
+ let(:current_user) { user }
+ let(:message) { '404 Merge Base Not Found' }
+ end
+ end
+
+ context 'when not enough refs are passed' do
+ let(:refs) { %w(only-one) }
+ let(:current_user) { user }
+
+ it 'renders a bad request error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq('Provide exactly 2 refs')
+ end
+ end
+ end
end
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index 6bb53fdc98d..d1e16ab9ca9 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -56,6 +56,8 @@ describe API::Templates do
end
it 'returns a license template' do
+ expect(response).to have_gitlab_http_status(200)
+
expect(json_response['key']).to eq('mit')
expect(json_response['name']).to eq('MIT License')
expect(json_response['nickname']).to be_nil
@@ -181,6 +183,7 @@ describe API::Templates do
it 'replaces the copyright owner placeholder with the name of the current user' do
get api('/templates/licenses/mit', user)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}")
end
end
diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb
new file mode 100644
index 00000000000..8f795bb561e
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_reference_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_reference'
+
+describe RuboCop::Cop::Migration::AddReference do
+ include CopHelper
+
+ let(:cop) { described_class.new }
+
+ context 'outside of a migration' do
+ it 'does not register any offenses' do
+ expect_no_offenses(<<~RUBY)
+ def up
+ add_reference(:projects, :users)
+ end
+ RUBY
+ end
+ end
+
+ context 'in a migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when using add_reference without index' do
+ expect_offense(<<~RUBY)
+ call do
+ add_reference(:projects, :users)
+ ^^^^^^^^^^^^^ `add_reference` requires `index: true`
+ end
+ RUBY
+ end
+
+ it 'registers an offense when using add_reference index disabled' do
+ expect_offense(<<~RUBY)
+ def up
+ add_reference(:projects, :users, index: false)
+ ^^^^^^^^^^^^^ `add_reference` requires `index: true`
+ end
+ RUBY
+ end
+
+ it 'does not register an offense when using add_reference with index enabled' do
+ expect_no_offenses(<<~RUBY)
+ def up
+ add_reference(:projects, :users, index: true)
+ end
+ RUBY
+ end
+ end
+end
diff --git a/spec/serializers/project_mirror_serializer_spec.rb b/spec/serializers/project_mirror_serializer_spec.rb
new file mode 100644
index 00000000000..5e47163532a
--- /dev/null
+++ b/spec/serializers/project_mirror_serializer_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+describe ProjectMirrorSerializer do
+ it 'represents ProjectMirror entities' do
+ expect(described_class.entity_class).to eq(ProjectMirrorEntity)
+ end
+end
diff --git a/spec/services/ci/enqueue_build_service_spec.rb b/spec/services/ci/enqueue_build_service_spec.rb
new file mode 100644
index 00000000000..e41b8e4800b
--- /dev/null
+++ b/spec/services/ci/enqueue_build_service_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Ci::EnqueueBuildService, '#execute' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:ci_build) { create(:ci_build, :created) }
+
+ subject { described_class.new(project, user).execute(ci_build) }
+
+ it 'enqueues the build' do
+ subject
+
+ expect(ci_build.pending?).to be_truthy
+ end
+end
diff --git a/spec/services/commits/tag_service_spec.rb b/spec/services/commits/tag_service_spec.rb
new file mode 100644
index 00000000000..82377a8dace
--- /dev/null
+++ b/spec/services/commits/tag_service_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Commits::TagService do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ let(:commit) { project.commit }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe '#execute' do
+ let(:service) { described_class.new(project, user, opts) }
+
+ shared_examples 'tag failure' do
+ it 'returns a hash with the :error status' do
+ result = service.execute(commit)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
+
+ it 'does not add a system note' do
+ service.execute(commit)
+
+ description_notes = find_notes('tag')
+ expect(description_notes).to be_empty
+ end
+ end
+
+ def find_notes(action)
+ commit
+ .notes
+ .joins(:system_note_metadata)
+ .where(system_note_metadata: { action: action })
+ end
+
+ context 'valid params' do
+ let(:opts) do
+ {
+ tag_name: 'v1.2.3',
+ tag_message: 'Release'
+ }
+ end
+
+ def find_notes(action)
+ commit
+ .notes
+ .joins(:system_note_metadata)
+ .where(system_note_metadata: { action: action })
+ end
+
+ context 'when tagging succeeds' do
+ it 'returns a hash with the :success status and created tag' do
+ result = service.execute(commit)
+
+ expect(result[:status]).to eq(:success)
+
+ tag = result[:tag]
+ expect(tag.name).to eq(opts[:tag_name])
+ expect(tag.message).to eq(opts[:tag_message])
+ end
+
+ it 'adds a system note' do
+ service.execute(commit)
+
+ description_notes = find_notes('tag')
+ expect(description_notes.length).to eq(1)
+ end
+ end
+
+ context 'when tagging fails' do
+ let(:tag_error) { 'GitLab: You are not allowed to push code to this project.' }
+
+ before do
+ tag_stub = instance_double(Tags::CreateService)
+ allow(Tags::CreateService).to receive(:new).and_return(tag_stub)
+ allow(tag_stub).to receive(:execute).and_return({
+ status: :error, message: tag_error
+ })
+ end
+
+ it_behaves_like 'tag failure' do
+ let(:error_message) { tag_error }
+ end
+ end
+ end
+
+ context 'invalid params' do
+ let(:opts) do
+ {}
+ end
+
+ it_behaves_like 'tag failure' do
+ let(:error_message) { 'Missing parameter tag_name' }
+ end
+ end
+ end
+end
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index 784dac55454..a8c994c101c 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -11,40 +11,6 @@ describe Notes::QuickActionsService do
end
end
- shared_examples 'note on noteable that does not support quick actions' do
- include_context 'note on noteable'
-
- before do
- note.note = note_text
- end
-
- describe 'note with only command' do
- describe '/close, /label, /assign & /milestone' do
- let(:note_text) { %(/close\n/assign @#{assignee.username}") }
-
- it 'saves the note and does not alter the note text' do
- content, command_params = service.extract_commands(note)
-
- expect(content).to eq note_text
- expect(command_params).to be_empty
- end
- end
- end
-
- describe 'note with command & text' do
- describe '/close, /label, /assign & /milestone' do
- let(:note_text) { %(HELLO\n/close\n/assign @#{assignee.username}\nWORLD) }
-
- it 'saves the note and does not alter the note text' do
- content, command_params = service.extract_commands(note)
-
- expect(content).to eq note_text
- expect(command_params).to be_empty
- end
- end
- end
- end
-
shared_examples 'note on noteable that supports quick actions' do
include_context 'note on noteable'
@@ -147,16 +113,16 @@ describe Notes::QuickActionsService do
expect(described_class.noteable_update_service(note)).to eq(Issues::UpdateService)
end
- it 'returns Issues::UpdateService for a note on a merge request' do
+ it 'returns MergeRequests::UpdateService for a note on a merge request' do
note = create(:note_on_merge_request, project: project)
expect(described_class.noteable_update_service(note)).to eq(MergeRequests::UpdateService)
end
- it 'returns nil for a note on a commit' do
+ it 'returns Commits::TagService for a note on a commit' do
note = create(:note_on_commit, project: project)
- expect(described_class.noteable_update_service(note)).to be_nil
+ expect(described_class.noteable_update_service(note)).to eq(Commits::TagService)
end
end
@@ -175,7 +141,7 @@ describe Notes::QuickActionsService do
let(:note) { create(:note_on_commit, project: project) }
it 'returns false' do
- expect(described_class.supported?(note)).to be_falsy
+ expect(described_class.supported?(note)).to be_truthy
end
end
end
@@ -203,10 +169,6 @@ describe Notes::QuickActionsService do
it_behaves_like 'note on noteable that supports quick actions' do
let(:note) { build(:note_on_merge_request, project: project) }
end
-
- it_behaves_like 'note on noteable that does not support quick actions' do
- let(:note) { build(:note_on_commit, project: project) }
- end
end
context 'CE restriction for issue assignees' do
diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb
index 81dc7c57f4a..507909d9231 100644
--- a/spec/services/preview_markdown_service_spec.rb
+++ b/spec/services/preview_markdown_service_spec.rb
@@ -65,6 +65,31 @@ describe PreviewMarkdownService do
end
end
+ context 'commit description' do
+ let(:project) { create(:project, :repository) }
+ let(:commit) { project.commit }
+ let(:params) do
+ {
+ text: "My work\n/tag v1.2.3 Stable release",
+ quick_actions_target_type: 'Commit',
+ quick_actions_target_id: commit.id
+ }
+ end
+ let(:service) { described_class.new(project, user, params) }
+
+ it 'removes quick actions from text' do
+ result = service.execute
+
+ expect(result[:text]).to eq 'My work'
+ end
+
+ it 'explains quick actions effect' do
+ result = service.execute
+
+ expect(result[:commands]).to eq 'Tags this commit to v1.2.3 with "Stable release".'
+ end
+ end
+
it 'sets correct markdown engine' do
service = described_class.new(project, user, { markdown_version: CacheMarkdownField::CACHE_REDCARPET_VERSION })
result = service.execute
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 743e281183e..bf1c157c4a2 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -6,6 +6,7 @@ describe QuickActions::InterpretService do
let(:developer2) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:milestone) { create(:milestone, project: project, title: '9.10') }
+ let(:commit) { create(:commit, project: project) }
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
let(:bug) { create(:label, project: project, title: 'Bug') }
let(:note) { build(:note, commit_id: merge_request.diff_head_sha) }
@@ -347,6 +348,14 @@ describe QuickActions::InterpretService do
end
end
+ shared_examples 'tag command' do
+ it 'tags a commit' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(tag_name: tag_name, tag_message: tag_message)
+ end
+ end
+
it_behaves_like 'reopen command' do
let(:content) { '/reopen' }
let(:issuable) { issue }
@@ -628,16 +637,6 @@ describe QuickActions::InterpretService do
let(:issuable) { merge_request }
end
- it_behaves_like 'todo command' do
- let(:content) { '/todo' }
- let(:issuable) { issue }
- end
-
- it_behaves_like 'todo command' do
- let(:content) { '/todo' }
- let(:issuable) { merge_request }
- end
-
it_behaves_like 'done command' do
let(:content) { '/done' }
let(:issuable) { issue }
@@ -787,6 +786,28 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
+ context '/todo' do
+ let(:content) { '/todo' }
+
+ context 'if issuable is an Issue' do
+ it_behaves_like 'todo command' do
+ let(:issuable) { issue }
+ end
+ end
+
+ context 'if issuable is a MergeRequest' do
+ it_behaves_like 'todo command' do
+ let(:issuable) { merge_request }
+ end
+ end
+
+ context 'if issuable is a Commit' do
+ it_behaves_like 'empty command' do
+ let(:issuable) { commit }
+ end
+ end
+ end
+
context '/copy_metadata command' do
let(:todo_label) { create(:label, project: project, title: 'To Do') }
let(:inreview_label) { create(:label, project: project, title: 'In Review') }
@@ -971,6 +992,12 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
end
+
+ context 'if issuable is a Commit' do
+ let(:content) { '/award :100:' }
+ let(:issuable) { commit }
+ it_behaves_like 'empty command'
+ end
end
context '/shrug command' do
@@ -1102,6 +1129,32 @@ describe QuickActions::InterpretService do
it_behaves_like 'empty command'
end
end
+
+ context '/tag command' do
+ let(:issuable) { commit }
+
+ context 'ignores command with no argument' do
+ it_behaves_like 'empty command' do
+ let(:content) { '/tag' }
+ end
+ end
+
+ context 'tags a commit with a tag name' do
+ it_behaves_like 'tag command' do
+ let(:tag_name) { 'v1.2.3' }
+ let(:tag_message) { nil }
+ let(:content) { "/tag #{tag_name}" }
+ end
+ end
+
+ context 'tags a commit with a tag name and message' do
+ it_behaves_like 'tag command' do
+ let(:tag_name) { 'v1.2.3' }
+ let(:tag_message) { 'Stable release' }
+ let(:content) { "/tag #{tag_name} #{tag_message}" }
+ end
+ end
+ end
end
describe '#explain' do
@@ -1319,5 +1372,39 @@ describe QuickActions::InterpretService do
expect(explanations).to eq(["Moves this issue to test/project."])
end
end
+
+ describe 'tag a commit' do
+ describe 'with a tag name' do
+ context 'without a message' do
+ let(:content) { '/tag v1.2.3' }
+
+ it 'includes the tag name only' do
+ _, explanations = service.explain(content, commit)
+
+ expect(explanations).to eq(["Tags this commit to v1.2.3."])
+ end
+ end
+
+ context 'with an empty message' do
+ let(:content) { '/tag v1.2.3 ' }
+
+ it 'includes the tag name only' do
+ _, explanations = service.explain(content, commit)
+
+ expect(explanations).to eq(["Tags this commit to v1.2.3."])
+ end
+ end
+ end
+
+ describe 'with a tag name and message' do
+ let(:content) { '/tag v1.2.3 Stable release' }
+
+ it 'includes the tag name and message' do
+ _, explanations = service.explain(content, commit)
+
+ expect(explanations).to eq(["Tags this commit to v1.2.3 with \"Stable release\"."])
+ end
+ end
+ end
end
end
diff --git a/spec/services/quick_actions/target_service_spec.rb b/spec/services/quick_actions/target_service_spec.rb
new file mode 100644
index 00000000000..0aeb29cbeec
--- /dev/null
+++ b/spec/services/quick_actions/target_service_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe QuickActions::TargetService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe '#execute' do
+ shared_examples 'no target' do |type_id:|
+ it 'returns nil' do
+ target = service.execute(type, type_id)
+
+ expect(target).to be_nil
+ end
+ end
+
+ shared_examples 'find target' do
+ it 'returns the target' do
+ found_target = service.execute(type, target_id)
+
+ expect(found_target).to eq(target)
+ end
+ end
+
+ shared_examples 'build target' do |type_id:|
+ it 'builds a new target' do
+ target = service.execute(type, type_id)
+
+ expect(target.project).to eq(project)
+ expect(target).to be_new_record
+ end
+ end
+
+ context 'for issue' do
+ let(:target) { create(:issue, project: project) }
+ let(:target_id) { target.iid }
+ let(:type) { 'Issue' }
+
+ it_behaves_like 'find target'
+ it_behaves_like 'build target', type_id: nil
+ it_behaves_like 'build target', type_id: -1
+ end
+
+ context 'for merge request' do
+ let(:target) { create(:merge_request, source_project: project) }
+ let(:target_id) { target.iid }
+ let(:type) { 'MergeRequest' }
+
+ it_behaves_like 'find target'
+ it_behaves_like 'build target', type_id: nil
+ it_behaves_like 'build target', type_id: -1
+ end
+
+ context 'for commit' do
+ let(:project) { create(:project, :repository) }
+ let(:target) { project.commit.parent }
+ let(:target_id) { target.sha }
+ let(:type) { 'Commit' }
+
+ it_behaves_like 'find target'
+ it_behaves_like 'no target', type_id: 'invalid_sha'
+
+ context 'with nil target_id' do
+ let(:target) { project.commit }
+ let(:target_id) { nil }
+
+ it_behaves_like 'find target'
+ end
+ end
+
+ context 'for unknown type' do
+ let(:type) { 'unknown' }
+
+ it_behaves_like 'no target', type_id: :unused
+ end
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 57d081cffb3..442de61f69b 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -108,6 +108,25 @@ describe SystemNoteService do
end
end
+ describe '.tag_commit' do
+ let(:noteable) do
+ project.commit
+ end
+ let(:tag_name) { 'v1.2.3' }
+
+ subject { described_class.tag_commit(noteable, project, author, tag_name) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'tag' }
+ end
+
+ it 'sets the note text' do
+ link = "http://localhost/#{project.full_path}/tags/#{tag_name}"
+
+ expect(subject.note).to eq "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
+ end
+ end
+
describe '.change_assignee' do
subject { described_class.change_assignee(noteable, project, author, assignee) }
diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb
index eb5da662ab5..476d80f3a93 100644
--- a/spec/support/banzai/reference_filter_shared_examples.rb
+++ b/spec/support/banzai/reference_filter_shared_examples.rb
@@ -11,3 +11,76 @@ shared_examples 'a reference containing an element node' do
expect(doc.children.first.inner_html).to eq(inner_html)
end
end
+
+# Requires a reference, subject and subject_name:
+# subject { create(:user) }
+# let(:reference) { subject.to_reference }
+# let(:subject_name) { 'user' }
+shared_examples 'user reference or project reference' do
+ shared_examples 'it contains a data- attribute' do
+ it 'includes a data- attribute' do
+ doc = reference_filter("Hey #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute("data-#{subject_name}")
+ expect(link.attr("data-#{subject_name}")).to eq subject.id.to_s
+ end
+ end
+
+ context 'mentioning a resource' do
+ it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'it contains a data- attribute'
+
+ it "links to a resource" do
+ doc = reference_filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('href')).to eq urls.send("#{subject_name}_url", subject)
+ end
+
+ it 'links to a resource with a period' do
+ subject = create(subject_name.to_sym, name: 'alphA.Beta')
+
+ doc = reference_filter("Hey #{get_reference(subject)}")
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'links to a resource with an underscore' do
+ subject = create(subject_name.to_sym, name: 'ping_pong_king')
+
+ doc = reference_filter("Hey #{get_reference(subject)}")
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'links to a resource with different case-sensitivity' do
+ subject = create(subject_name.to_sym, name: 'RescueRanger')
+ reference = get_reference(subject)
+
+ doc = reference_filter("Hey #{reference.upcase}")
+ expect(doc.css('a').length).to eq 1
+ expect(doc.css('a').text).to eq(reference)
+ end
+ end
+
+ it 'supports an :only_path context' do
+ doc = reference_filter("Hey #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.send "#{subject_name}_path", subject
+ end
+
+ context 'referencing a resource in a link href' do
+ let(:reference) { %Q{<a href="#{get_reference(subject)}">Some text</a>} }
+
+ it_behaves_like 'it contains a data- attribute'
+
+ it 'links to the resource' do
+ doc = reference_filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('href')).to eq urls.send "#{subject_name}_url", subject
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Mention me (#{reference}.)")
+ expect(doc.to_html).to match(%r{\(<a.+>Some text</a>\.\)})
+ end
+ end
+end
diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb
index 2b9f8b30c60..89517fde6e2 100644
--- a/spec/support/helpers/features/notes_helpers.rb
+++ b/spec/support/helpers/features/notes_helpers.rb
@@ -20,6 +20,13 @@ module Spec
end
end
end
+
+ def preview_note(text)
+ page.within('.js-main-target-form') do
+ fill_in('note[note]', with: text)
+ click_on('Preview')
+ end
+ end
end
end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index f392660d2c7..21103771d1f 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -67,6 +67,7 @@ module TestEnv
TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**')
REPOS_STORAGE = 'default'.freeze
+ BROKEN_STORAGE = 'broken'.freeze
# Test environment
#
@@ -157,10 +158,11 @@ module TestEnv
component_timed_setup('Gitaly',
install_dir: gitaly_dir,
version: Gitlab::GitalyClient.expected_server_version,
- task: "gitlab:gitaly:install[#{gitaly_dir}]") do
+ task: "gitlab:gitaly:install[#{gitaly_dir},#{repos_path}]") do
- # Always re-create config, in case it's outdated. This is fast anyway.
- Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, force: true)
+ # Re-create config, to specify the broken storage path
+ storage_paths = { 'default' => repos_path, 'broken' => broken_path }
+ Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, storage_paths, force: true)
start_gitaly(gitaly_dir)
end
@@ -256,6 +258,10 @@ module TestEnv
@repos_path ||= Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path
end
+ def broken_path
+ @broken_path ||= Gitlab.config.repositories.storages[BROKEN_STORAGE].legacy_disk_path
+ end
+
def backup_path
Gitlab.config.backup.path
end
diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb
index bbac6ca6a9c..b4164cff922 100644
--- a/spec/support/import_export/configuration_helper.rb
+++ b/spec/support/import_export/configuration_helper.rb
@@ -9,7 +9,7 @@ module ConfigurationHelper
end
def relation_class_for_name(relation_name)
- relation_name = Gitlab::ImportExport::RelationFactory::OVERRIDES[relation_name.to_sym] || relation_name
+ relation_name = Gitlab::ImportExport::RelationFactory.overrides[relation_name.to_sym] || relation_name
Gitlab::ImportExport::RelationFactory.relation_class(relation_name)
end
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index 4545226d78c..e6e4d9504d9 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -8,13 +8,23 @@ describe 'gitlab:gitaly namespace rake task' do
describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitaly.git' }
let(:clone_path) { Rails.root.join('tmp/tests/gitaly').to_s }
+ let(:storage_path) { Rails.root.join('tmp/tests/repositories').to_s }
let(:version) { File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp }
+ subject { run_rake_task('gitlab:gitaly:install', clone_path, storage_path) }
+
context 'no dir given' do
it 'aborts and display a help message' do
# avoid writing task output to spec progress
allow($stderr).to receive :write
- expect { run_rake_task('gitlab:gitaly:install') }.to raise_error /Please specify the directory where you want to install gitaly/
+ expect { run_rake_task('gitlab:gitaly:install') }.to raise_error /Please specify the directory where you want to install gitaly and the path for the default storage/
+ end
+ end
+
+ context 'no storage path given' do
+ it 'aborts and display a help message' do
+ allow($stderr).to receive :write
+ expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error /Please specify the directory where you want to install gitaly and the path for the default storage/
end
end
@@ -23,7 +33,7 @@ describe 'gitlab:gitaly namespace rake task' do
expect(main_object)
.to receive(:checkout_or_clone_version).and_raise 'Git error'
- expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error'
+ expect { subject }.to raise_error 'Git error'
end
end
@@ -36,7 +46,7 @@ describe 'gitlab:gitaly namespace rake task' do
expect(main_object)
.to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
- run_rake_task('gitlab:gitaly:install', clone_path)
+ subject
end
end
@@ -59,7 +69,7 @@ describe 'gitlab:gitaly namespace rake task' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
expect(main_object).to receive(:run_command!).with(command_preamble + %w[gmake]).and_return(true)
- run_rake_task('gitlab:gitaly:install', clone_path)
+ subject
end
end
@@ -72,7 +82,7 @@ describe 'gitlab:gitaly namespace rake task' do
it 'calls make in the gitaly directory' do
expect(main_object).to receive(:run_command!).with(command_preamble + %w[make]).and_return(true)
- run_rake_task('gitlab:gitaly:install', clone_path)
+ subject
end
context 'when Rails.env is test' do
@@ -89,55 +99,10 @@ describe 'gitlab:gitaly namespace rake task' do
it 'calls make in the gitaly directory with --no-deployment flag for bundle' do
expect(main_object).to receive(:run_command!).with(command_preamble + command).and_return(true)
- run_rake_task('gitlab:gitaly:install', clone_path)
+ subject
end
end
end
end
end
-
- describe 'storage_config' do
- it 'prints storage configuration in a TOML format' do
- config = {
- 'default' => Gitlab::GitalyClient::StorageSettings.new(
- 'path' => '/path/to/default',
- 'gitaly_address' => 'unix:/path/to/my.socket'
- ),
- 'nfs_01' => Gitlab::GitalyClient::StorageSettings.new(
- 'path' => '/path/to/nfs_01',
- 'gitaly_address' => 'unix:/path/to/my.socket'
- )
- }
- allow(Gitlab.config.repositories).to receive(:storages).and_return(config)
- allow(Rails.env).to receive(:test?).and_return(false)
-
- expected_output = ''
- Timecop.freeze do
- expected_output = <<~TOML
- # Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)}
- # This is in TOML format suitable for use in Gitaly's config.toml file.
- bin_dir = "tmp/tests/gitaly"
- socket_path = "/path/to/my.socket"
- [gitlab-shell]
- dir = "#{Gitlab.config.gitlab_shell.path}"
- [[storage]]
- name = "default"
- path = "/path/to/default"
- [[storage]]
- name = "nfs_01"
- path = "/path/to/nfs_01"
- TOML
- end
-
- expect { run_rake_task('gitlab:gitaly:storage_config')}
- .to output(expected_output).to_stdout
-
- parsed_output = TomlRB.parse(expected_output)
- config.each do |name, params|
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params.legacy_disk_path })
- end
- end
- end
- end
end
diff --git a/spec/views/shared/notes/_form.html.haml_spec.rb b/spec/views/shared/notes/_form.html.haml_spec.rb
index c57319869f3..0189f926a5f 100644
--- a/spec/views/shared/notes/_form.html.haml_spec.rb
+++ b/spec/views/shared/notes/_form.html.haml_spec.rb
@@ -16,7 +16,7 @@ describe 'shared/notes/_form' do
render
end
- %w[issue merge_request].each do |noteable|
+ %w[issue merge_request commit].each do |noteable|
context "with a note on #{noteable}" do
let(:note) { build(:"note_on_#{noteable}", project: project) }
@@ -25,12 +25,4 @@ describe 'shared/notes/_form' do
end
end
end
-
- context 'with a note on a commit' do
- let(:note) { build(:note_on_commit, project: project) }
-
- it 'says that only markdown is supported, not quick actions' do
- expect(rendered).to have_content('Markdown is supported')
- end
- end
end
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index b698248bc38..ffcf5648075 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -641,10 +641,10 @@ rollout 100%:
function install_dependencies() {
apk add -U openssl curl tar gzip bash ca-certificates git
- wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub
- wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk
- apk add glibc-2.23-r3.apk
- rm glibc-2.23-r3.apk
+ wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
+ wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
+ apk add glibc-2.28-r0.apk
+ rm glibc-2.28-r0.apk
curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
mv linux-amd64/helm /usr/bin/
@@ -720,10 +720,29 @@ rollout 100%:
if [[ -f Dockerfile ]]; then
echo "Building Dockerfile-based application..."
- docker build -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
+ docker build \
+ --build-arg HTTP_PROXY="$HTTP_PROXY" \
+ --build-arg http_proxy="$http_proxy" \
+ --build-arg HTTPS_PROXY="$HTTPS_PROXY" \
+ --build-arg https_proxy="$https_proxy" \
+ --build-arg FTP_PROXY="$FTP_PROXY" \
+ --build-arg ftp_proxy="$ftp_proxy" \
+ --build-arg NO_PROXY="$NO_PROXY" \
+ --build-arg no_proxy="$no_proxy" \
+ -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
else
echo "Building Heroku-based application using gliderlabs/herokuish docker image..."
- docker run -i -e BUILDPACK_URL --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build
+ docker run -i \
+ -e BUILDPACK_URL \
+ -e HTTP_PROXY \
+ -e http_proxy \
+ -e HTTPS_PROXY \
+ -e https_proxy \
+ -e FTP_PROXY \
+ -e ftp_proxy \
+ -e NO_PROXY \
+ -e no_proxy \
+ --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build
docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
docker rm "$CI_CONTAINER_NAME" >/dev/null
echo ""
diff --git a/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml
index 9f4cc0574d6..983d7b5250e 100644
--- a/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml
@@ -1,24 +1,25 @@
# Full project: https://gitlab.com/pages/middleman
-image: ruby:2.3
+image: ruby:2.4
+variables:
+ LANG: "C.UTF-8"
cache:
paths:
- vendor
-test:
- script:
+before_script:
- apt-get update -yqqq
- apt-get install -y nodejs
- bundle install --path vendor
+
+test:
+ script:
- bundle exec middleman build
except:
- master
pages:
script:
- - apt-get update -yqqq
- - apt-get install -y nodejs
- - bundle install --path vendor
- bundle exec middleman build
artifacts:
paths: