Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorConstance Okoghenun <cokoghenun@gitlab.com>2018-01-22 23:17:23 +0300
committerConstance Okoghenun <cokoghenun@gitlab.com>2018-01-22 23:17:23 +0300
commit5b17f8c56a5de0c020c8b231e9bc6fe8d04c3ae8 (patch)
tree163905ef6f612cbd3db260e2e3164ffbb75505b2
parent7328d53394819bf9a42bae2df4998854ad9c1184 (diff)
parentf27103c93c59df3e30ec05ebe279393d1a2e8930 (diff)
Resolved conflicts with master in dispatcher.js
-rw-r--r--.eslintrc1
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml22
-rw-r--r--.gitlab/merge_request_templates/Documentation.md2
-rw-r--r--.rubocop_todo.yml135
-rw-r--r--CHANGELOG.md223
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile13
-rw-r--r--Gemfile.lock33
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/api.js1
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js2
-rw-r--r--app/assets/javascripts/boards/components/board.js2
-rw-r--r--app/assets/javascripts/boards/components/board_list.js2
-rw-r--r--app/assets/javascripts/boards/utils/query_data.js2
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue16
-rw-r--r--app/assets/javascripts/commit_merge_requests.js73
-rw-r--r--app/assets/javascripts/create_item_dropdown.js (renamed from app/assets/javascripts/protected_tags/protected_tag_dropdown.js)67
-rw-r--r--app/assets/javascripts/create_merge_request_dropdown.js1
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue29
-rw-r--r--app/assets/javascripts/dispatcher.js552
-rw-r--r--app/assets/javascripts/dropzone_input.js2
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js1
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js1
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js1
-rw-r--r--app/assets/javascripts/flash.js4
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue9
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue5
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue11
-rw-r--r--app/assets/javascripts/ide/ide_router.js4
-rw-r--r--app/assets/javascripts/ide/lib/editor.js5
-rw-r--r--app/assets/javascripts/ide/stores/actions.js35
-rw-r--r--app/assets/javascripts/ide/stores/actions/branch.js2
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js18
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js2
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js4
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js2
-rw-r--r--app/assets/javascripts/ide/stores/utils.js2
-rw-r--r--app/assets/javascripts/init_labels.js18
-rw-r--r--app/assets/javascripts/issuable/auto_width_dropdown_select.js12
-rw-r--r--app/assets/javascripts/issuable_form.js40
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue18
-rw-r--r--app/assets/javascripts/job.js13
-rw-r--r--app/assets/javascripts/jobs/components/header.vue7
-rw-r--r--app/assets/javascripts/label_manager.js2
-rw-r--r--app/assets/javascripts/labels_select.js2
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js2
-rw-r--r--app/assets/javascripts/main.js201
-rw-r--r--app/assets/javascripts/merge_request.js15
-rw-r--r--app/assets/javascripts/milestone.js2
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue8
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue15
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue5
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue1
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue4
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js2
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue47
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/index.js29
-rw-r--r--app/assets/javascripts/pages/constants.js6
-rw-r--r--app/assets/javascripts/pages/dashboard/merge_requests/index.js7
-rw-r--r--app/assets/javascripts/pages/groups/activity/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index/index.js11
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js8
-rw-r--r--app/assets/javascripts/pages/groups/labels/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/labels/index/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/labels/new/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js8
-rw-r--r--app/assets/javascripts/pages/groups/milestones/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/milestones/new/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/new/index.js9
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js9
-rw-r--r--app/assets/javascripts/pages/groups/show/index.js19
-rw-r--r--app/assets/javascripts/pages/omniauth_callbacks/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/blame/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/blob/show/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/boards/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/branches/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/clusters/index/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/commit/pipelines/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js22
-rw-r--r--app/assets/javascripts/pages/projects/commits/show/index.js9
-rw-r--r--app/assets/javascripts/pages/projects/compare/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/compare/show/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/constants.js6
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js14
-rw-r--r--app/assets/javascripts/pages/projects/find_file/show/index.js12
-rw-r--r--app/assets/javascripts/pages/projects/forks/new/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/imports/show/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js33
-rw-r--r--app/assets/javascripts/pages/projects/init_form.js7
-rw-r--r--app/assets/javascripts/pages/projects/issues/edit/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js16
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js16
-rw-r--r--app/assets/javascripts/pages/projects/issues/new/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/issues/show/index.js13
-rw-r--r--app/assets/javascripts/pages/projects/labels/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/labels/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/labels/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js18
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js13
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js19
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/milestones/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/milestones/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/new/index.js9
-rw-r--r--app/assets/javascripts/pages/projects/project.js (renamed from app/assets/javascripts/project.js)4
-rw-r--r--app/assets/javascripts/pages/projects/releases/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js18
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue (renamed from app/assets/javascripts/projects/permissions/components/project_feature_setting.vue)2
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue (renamed from app/assets/javascripts/projects/permissions/components/project_setting_row.vue)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue (renamed from app/assets/javascripts/projects/permissions/components/settings_panel.vue)2
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/constants.js (renamed from app/assets/javascripts/projects/permissions/constants.js)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/external.js (renamed from app/assets/javascripts/projects/permissions/external.js)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/index.js (renamed from app/assets/javascripts/projects/permissions/index.js)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_avatar.js (renamed from app/assets/javascripts/project_avatar.js)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_new.js (renamed from app/assets/javascripts/project_new.js)2
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js23
-rw-r--r--app/assets/javascripts/pages/projects/snippets/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/snippets/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/snippets/show/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/tags/new/index.js9
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js15
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js11
-rw-r--r--app/assets/javascripts/pages/projects/wikis/wikis.js (renamed from app/assets/javascripts/wikis.js)4
-rw-r--r--app/assets/javascripts/pages/search/init_filtered_search.js7
-rw-r--r--app/assets/javascripts/pages/search/show/search.js2
-rw-r--r--app/assets/javascripts/pages/sessions/index.js5
-rw-r--r--app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js6
-rw-r--r--app/assets/javascripts/pages/snippets/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/snippets/form.js7
-rw-r--r--app/assets/javascripts/pages/snippets/new/index.js3
-rw-r--r--app/assets/javascripts/pipelines/components/async_button.vue12
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue1
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue9
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue6
-rw-r--r--app/assets/javascripts/projects/project_new.js2
-rw-r--r--app/assets/javascripts/projects_dropdown/index.js7
-rw-r--r--app/assets/javascripts/projects_dropdown/service/projects_service.js1
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js12
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_dropdown.js90
-rw-r--r--app/assets/javascripts/protected_tags/protected_tag_create.js11
-rw-r--r--app/assets/javascripts/render_mermaid.js6
-rw-r--r--app/assets/javascripts/shared/milestones/form.js9
-rw-r--r--app/assets/javascripts/shared/sessions/u2f.js16
-rw-r--r--app/assets/javascripts/shortcuts.js8
-rw-r--r--app/assets/javascripts/shortcuts_blob.js2
-rw-r--r--app/assets/javascripts/shortcuts_find_file.js3
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js8
-rw-r--r--app/assets/javascripts/shortcuts_navigation.js3
-rw-r--r--app/assets/javascripts/shortcuts_network.js2
-rw-r--r--app/assets/javascripts/shortcuts_wiki.js8
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.js2
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue65
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js26
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue31
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js47
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue52
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/dependencies.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/index.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_icon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/modal.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue127
-rw-r--r--app/assets/javascripts/vue_shared/components/toggle_button.vue6
-rw-r--r--app/assets/javascripts/zen_mode.js5
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/awards.scss11
-rw-r--r--app/assets/stylesheets/framework/buttons.scss33
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss17
-rw-r--r--app/assets/stylesheets/framework/files.scss4
-rw-r--r--app/assets/stylesheets/framework/header.scss7
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss8
-rw-r--r--app/assets/stylesheets/framework/layout.scss8
-rw-r--r--app/assets/stylesheets/framework/mixins.scss2
-rw-r--r--app/assets/stylesheets/framework/modal.scss33
-rw-r--r--app/assets/stylesheets/framework/stacked-progress-bar.scss54
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss8
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss41
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss8
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss10
-rw-r--r--app/assets/stylesheets/pages/projects.scss11
-rw-r--r--app/assets/stylesheets/pages/repo.scss74
-rw-r--r--app/assets/stylesheets/pages/xterm.scss29
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb4
-rw-r--r--app/controllers/admin/hooks_controller.rb6
-rw-r--r--app/controllers/admin/jobs_controller.rb2
-rw-r--r--app/controllers/concerns/group_tree.rb6
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/controllers/concerns/with_performance_bar.rb15
-rw-r--r--app/controllers/confirmations_controller.rb2
-rw-r--r--app/controllers/groups/milestones_controller.rb6
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb9
-rw-r--r--app/controllers/projects/commit_controller.rb14
-rw-r--r--app/controllers/projects/commits_controller.rb46
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb9
-rw-r--r--app/controllers/projects/hooks_controller.rb10
-rw-r--r--app/controllers/projects/jobs_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb6
-rw-r--r--app/controllers/projects/milestones_controller.rb14
-rw-r--r--app/finders/group_descendants_finder.rb41
-rw-r--r--app/finders/labels_finder.rb2
-rw-r--r--app/finders/milestones_finder.rb8
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/helpers/emails_helper.rb16
-rw-r--r--app/helpers/issuables_helper.rb8
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/helpers/todos_helper.rb10
-rw-r--r--app/helpers/webpack_helper.rb14
-rw-r--r--app/mailers/emails/issues.rb33
-rw-r--r--app/mailers/emails/merge_requests.rb37
-rw-r--r--app/mailers/notify.rb2
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/commit.rb17
-rw-r--r--app/models/concerns/resolvable_discussion.rb25
-rw-r--r--app/models/concerns/sha_attribute.rb1
-rw-r--r--app/models/concerns/triggerable_hooks.rb40
-rw-r--r--app/models/deploy_key.rb20
-rw-r--r--app/models/deploy_keys_project.rb10
-rw-r--r--app/models/global_milestone.rb8
-rw-r--r--app/models/hooks/project_hook.rb26
-rw-r--r--app/models/hooks/system_hook.rb16
-rw-r--r--app/models/hooks/web_hook.rb1
-rw-r--r--app/models/merge_request.rb15
-rw-r--r--app/models/merge_request_diff.rb3
-rw-r--r--app/models/notification_reason.rb19
-rw-r--r--app/models/notification_recipient.rb36
-rw-r--r--app/models/project.rb22
-rw-r--r--app/models/project_statistics.rb2
-rw-r--r--app/models/push_event.rb3
-rw-r--r--app/models/repository.rb48
-rw-r--r--app/models/route.rb14
-rw-r--r--app/models/service.rb5
-rw-r--r--app/models/user.rb12
-rw-r--r--app/presenters/projects/settings/deploy_keys_presenter.rb2
-rw-r--r--app/serializers/deploy_key_entity.rb9
-rw-r--r--app/serializers/deploy_keys_project_entity.rb4
-rw-r--r--app/services/labels/promote_service.rb12
-rw-r--r--app/services/merge_requests/create_from_issue_service.rb1
-rw-r--r--app/services/merge_requests/create_service.rb28
-rw-r--r--app/services/notification_recipient_service.rb48
-rw-r--r--app/services/notification_service.rb26
-rw-r--r--app/services/projects/gitlab_projects_import_service.rb2
-rw-r--r--app/services/system_hooks_service.rb7
-rw-r--r--app/services/system_note_service.rb23
-rw-r--r--app/services/test_hooks/base_service.rb2
-rw-r--r--app/services/test_hooks/system_service.rb7
-rw-r--r--app/services/web_hook_service.rb2
-rw-r--r--app/views/admin/dashboard/index.html.haml56
-rw-r--r--app/views/admin/deploy_keys/index.html.haml8
-rw-r--r--app/views/admin/hooks/_form.html.haml7
-rw-r--r--app/views/admin/hooks/edit.html.haml2
-rw-r--r--app/views/admin/hooks/index.html.haml4
-rw-r--r--app/views/admin/jobs/index.html.haml7
-rw-r--r--app/views/dashboard/_activity_head.html.haml4
-rw-r--r--app/views/dashboard/_snippets_head.html.haml4
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/devise/shared/_signin_box.html.haml2
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml2
-rw-r--r--app/views/ide/index.html.haml5
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml12
-rw-r--r--app/views/layouts/nav_only.html.haml5
-rw-r--r--app/views/layouts/notify.html.haml2
-rw-r--r--app/views/layouts/notify.text.erb2
-rw-r--r--app/views/profiles/gpg_keys/index.html.haml4
-rw-r--r--app/views/profiles/preferences/show.html.haml17
-rw-r--r--app/views/projects/_export.html.haml2
-rw-r--r--app/views/projects/_new_project_fields.html.haml2
-rw-r--r--app/views/projects/blob/_new_dir.html.haml2
-rw-r--r--app/views/projects/blob/_upload.html.haml2
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml41
-rw-r--r--app/views/projects/commit/_commit_box.html.haml6
-rw-r--r--app/views/projects/commits/_commit.atom.builder4
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml18
-rw-r--r--app/views/projects/forks/index.html.haml4
-rw-r--r--app/views/projects/hooks/edit.html.haml2
-rw-r--r--app/views/projects/jobs/_empty_state.html.haml5
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml2
-rw-r--r--app/views/projects/jobs/_table.html.haml2
-rw-r--r--app/views/projects/jobs/show.html.haml7
-rw-r--r--app/views/projects/merge_requests/creations/_diffs.html.haml6
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml2
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_dropdown.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_dropdown.html.haml2
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml3
-rw-r--r--app/views/projects/settings/integrations/_project_hook.html.haml4
-rw-r--r--app/views/projects/tree/_tree_header.html.haml4
-rw-r--r--app/views/shared/deploy_keys/_form.html.haml19
-rw-r--r--app/views/shared/issuable/_filter.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml6
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml2
-rw-r--r--app/views/shared/issuable/form/_branch_chooser.html.haml5
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/shared/web_hooks/_form.html.haml2
-rw-r--r--app/workers/concerns/project_import_options.rb4
-rw-r--r--app/workers/repository_fork_worker.rb5
-rwxr-xr-xbin/profile-url57
-rw-r--r--changelogs/unreleased-ee/4378-fix-cluster-js-not-running-on-update-page.yml5
-rw-r--r--changelogs/unreleased/13695-order-contributors-in-api.yml5
-rw-r--r--changelogs/unreleased/15832-fix-access-level-update-for-requesters.yml5
-rw-r--r--changelogs/unreleased/15922-validate-file-status-when-commiting-multiple-files.yml5
-rw-r--r--changelogs/unreleased/15955-improve-search-query.yml5
-rw-r--r--changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml5
-rw-r--r--changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml5
-rw-r--r--changelogs/unreleased/16468-add-fast-blank.yml5
-rw-r--r--changelogs/unreleased/19493-fork-does-not-protect-default-branch.yml5
-rw-r--r--changelogs/unreleased/20035-pause-resume-runners.yml5
-rw-r--r--changelogs/unreleased/24347-dont-post-system-note-when-branch-creation-fails.yml5
-rw-r--r--changelogs/unreleased/25317-prioritize-author-date-over-commit.yml5
-rw-r--r--changelogs/unreleased/26296-update-styling-disabled-buttons.yml5
-rw-r--r--changelogs/unreleased/28004-consider-refactoring-member-view-by-using-presenter.yml4
-rw-r--r--changelogs/unreleased/31995-project-limit-default-fix.yml5
-rw-r--r--changelogs/unreleased/32364-updating-slack-notification-not-working-by-api.yml5
-rw-r--r--changelogs/unreleased/33028-event-tag-links.yml5
-rw-r--r--changelogs/unreleased/33609-hide-pagination.yml5
-rw-r--r--changelogs/unreleased/33926-update-issuable-icons.yml5
-rw-r--r--changelogs/unreleased/34055-issues-enabled-filter-misbehavior.yml6
-rw-r--r--changelogs/unreleased/34252-trailing-plus.yml5
-rw-r--r--changelogs/unreleased/34534-switch-to-axios.yml5
-rw-r--r--changelogs/unreleased/36020-private-npm-modules.yml5
-rw-r--r--changelogs/unreleased/36571-ignore-root-in-repo.yml5
-rw-r--r--changelogs/unreleased/36669-default-mr-title-with-external-issues.yml5
-rw-r--r--changelogs/unreleased/36782-replace-team-user-role-with-add_role-user-in-specs.yml5
-rw-r--r--changelogs/unreleased/36958-enable-ordering-projects-subgroups-by-name.yml5
-rw-r--r--changelogs/unreleased/37199-labels-fix.yml5
-rw-r--r--changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml5
-rw-r--r--changelogs/unreleased/37898-increase-readability-of-colored-text-in-job-output-log.yml5
-rw-r--r--changelogs/unreleased/38019-hide-runner-token.yml5
-rw-r--r--changelogs/unreleased/38030-add-graph-value-to-hover.yml5
-rw-r--r--changelogs/unreleased/38145_ux_issues_in_system_info_page.yml5
-rw-r--r--changelogs/unreleased/38239-update-toggle-design.yml5
-rw-r--r--changelogs/unreleased/38318-search-merge-requests-with-api.yml5
-rw-r--r--changelogs/unreleased/38540-ssh-env-file.yml6
-rw-r--r--changelogs/unreleased/38541-cancel-alignment.yml5
-rw-r--r--changelogs/unreleased/38596-fix-backspace-visual-token-clearing.yml5
-rw-r--r--changelogs/unreleased/38893-banzai-upload-filter-relative-urls.yml5
-rw-r--r--changelogs/unreleased/39246-fork-and-import-jobs-should-only-be-marked-as-failed-when-the-number-of-retries-was-exhausted.yml5
-rw-r--r--changelogs/unreleased/39298-list-of-avatars-2.yml5
-rw-r--r--changelogs/unreleased/39608-comment-on-image-discussions-tab-alignment.yml5
-rw-r--r--changelogs/unreleased/3968-protected-branch-is-not-set-for-default-branch-on-import.yml5
-rw-r--r--changelogs/unreleased/39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled.yml5
-rw-r--r--changelogs/unreleased/40029-better-error-handling-on-issuable-templates.yml5
-rw-r--r--changelogs/unreleased/40031-include-assset_sync-gem.yml5
-rw-r--r--changelogs/unreleased/40040-decouple-multi-file-editor-from-file-list.yml5
-rw-r--r--changelogs/unreleased/40063-markdown-editor-improvements.yml5
-rw-r--r--changelogs/unreleased/40190-fix-slash-commands-dropdown-description-mis-alignement-on-firefox.yml5
-rw-r--r--changelogs/unreleased/40228-verify-integrity-of-repositories.yml5
-rw-r--r--changelogs/unreleased/40274-user-settings-breadcrumbs.yml5
-rw-r--r--changelogs/unreleased/40301-rebase.yml5
-rw-r--r--changelogs/unreleased/40418-migrate-existing-data-from-kubernetesservice-to-clusters-platforms-kubernetes.yml5
-rw-r--r--changelogs/unreleased/40453-fix-api-endpoints-to-edit-wiki-pages-where-project-belongs-to-a-group.yml5
-rw-r--r--changelogs/unreleased/40492-update-admin-dashboard-content-order.yml5
-rw-r--r--changelogs/unreleased/40509_sorting_tags_api.yml5
-rw-r--r--changelogs/unreleased/40533-groups-tree-updates.yml6
-rw-r--r--changelogs/unreleased/40549-render-emoj-in-groups-overview.yml5
-rw-r--r--changelogs/unreleased/40622-use-left-right-and-max-count.yml6
-rw-r--r--changelogs/unreleased/40780-choose-file.yml5
-rw-r--r--changelogs/unreleased/40818-last-push-widget-does-not-appear-after-pushing-new-commit.yml5
-rw-r--r--changelogs/unreleased/40871-todo-notification-count-shows-notification-without-having-a-todo.yml5
-rw-r--r--changelogs/unreleased/40895-fix-frequent-projects-stale-path.yml5
-rw-r--r--changelogs/unreleased/41016-import-gitlab-shell-projects.yml6
-rw-r--r--changelogs/unreleased/41053-extend-cluster-applications-to-allow-install-to-prometheus.yml5
-rw-r--r--changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml6
-rw-r--r--changelogs/unreleased/41056-create-cluster-from-kubernetes-integration-application-template.yml5
-rw-r--r--changelogs/unreleased/41118-add-sorting-to-deployments-api.yml5
-rw-r--r--changelogs/unreleased/41163-improve-cluster-ingress-extra-cost-language.yml5
-rw-r--r--changelogs/unreleased/41206-show-signin-pane-after-email-confirmation.yml5
-rw-r--r--changelogs/unreleased/41208-commit-atom-feeds-double-escaped.yml5
-rw-r--r--changelogs/unreleased/41244-issue-board-shortcut-working-while-no-issues.yml5
-rw-r--r--changelogs/unreleased/41247-timestamp.yml6
-rw-r--r--changelogs/unreleased/41249-clearing-the-cache.yml5
-rw-r--r--changelogs/unreleased/41268-bump-ruby-to-2-3-6.yml5
-rw-r--r--changelogs/unreleased/41424-gitlab-rake-gitlab-import-repos-schedules-an-import.yml5
-rw-r--r--changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml5
-rw-r--r--changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml5
-rw-r--r--changelogs/unreleased/41491-fix-nil-blob-name-error.yml5
-rw-r--r--changelogs/unreleased/41532-email-reason.yml5
-rw-r--r--changelogs/unreleased/41546-count-query-for-issues-and-mrs-runs-twice-on-group-index.yml5
-rw-r--r--changelogs/unreleased/41666-cannot-search-with-keyword-merge-2.yml5
-rw-r--r--changelogs/unreleased/41666-cannot-search-with-keyword-merge.yml6
-rw-r--r--changelogs/unreleased/41673-blank-query-members-api.yml5
-rw-r--r--changelogs/unreleased/41731-predicate-memoization.yml5
-rw-r--r--changelogs/unreleased/41743-unused-selectors-for-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/41749-postgres-9-6-for-ci-tests.yml5
-rw-r--r--changelogs/unreleased/41754-update-scss-lint-to-0-56-0.yml5
-rw-r--r--changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml5
-rw-r--r--changelogs/unreleased/41807-15665-consistently-502s-because-it-fetches-every-commit.yml6
-rw-r--r--changelogs/unreleased/41814-text-decoration-skip.yml5
-rw-r--r--changelogs/unreleased/41956-fix-ctrl-enter-binding-to-save-comment.yml5
-rw-r--r--changelogs/unreleased/42047-pg-10-support.yml5
-rw-r--r--changelogs/unreleased/42055-update-marked-from-0-3-6-to-0-3-12.yml5
-rw-r--r--changelogs/unreleased/42154-fix-artifact-size-calc.yml5
-rw-r--r--changelogs/unreleased/42157-41989-fix-duplicate-in-create-item-dropdown.yml5
-rw-r--r--changelogs/unreleased/42159-utf8-uploads.yml5
-rw-r--r--changelogs/unreleased/42206-permit-password-for-git-param.yml5
-rw-r--r--changelogs/unreleased/42231-protected-branches-api-route-returns-404-for-branches-with-dots.yml5
-rw-r--r--changelogs/unreleased/ac-autodevopfix-kubectl-version.yml5
-rw-r--r--changelogs/unreleased/add-tcp-check-rake-task.yml5
-rw-r--r--changelogs/unreleased/anchor-issue-references.yml6
-rw-r--r--changelogs/unreleased/api-domains-expose-project_id.yml5
-rw-r--r--changelogs/unreleased/bump_mysql_gem.yml5
-rw-r--r--changelogs/unreleased/bvl-fork-public-project-to-private-namespace.yml5
-rw-r--r--changelogs/unreleased/bvl-parent-preloading.yml5
-rw-r--r--changelogs/unreleased/change-issues-closed-at-background-migration.yml5
-rw-r--r--changelogs/unreleased/changes-dropdown-ellipsis.yml5
-rw-r--r--changelogs/unreleased/conditionally-eager-load-event-target-authors.yml5
-rw-r--r--changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml5
-rw-r--r--changelogs/unreleased/delay-background-migrations.yml5
-rw-r--r--changelogs/unreleased/display-mr-in-commit-page.yml5
-rw-r--r--changelogs/unreleased/dm-diff-note-for-line-performance.yml5
-rw-r--r--changelogs/unreleased/docs-add-why-do-i-get-signed-out-authentication-section.yml5
-rw-r--r--changelogs/unreleased/feat-add-section-headers-to-plus-button-dropdown.yml5
-rw-r--r--changelogs/unreleased/feature-39591-visibility-level.yml5
-rw-r--r--changelogs/unreleased/feature-40842-provide-oracles-webgate-cookies-to-jira-requests.yml6
-rw-r--r--changelogs/unreleased/feature-api_runners_online.yml5
-rw-r--r--changelogs/unreleased/feature-merge-request-system-hook.yml5
-rw-r--r--changelogs/unreleased/file-content-large-screen-padding.yml5
-rw-r--r--changelogs/unreleased/fix-abuse-reports-link-url.yml5
-rw-r--r--changelogs/unreleased/fix-activity-inline-event-line-height.yml5
-rw-r--r--changelogs/unreleased/fix-add-horizontal-scroll-to-wiki-tables.yml5
-rw-r--r--changelogs/unreleased/fix-create-mr-from-issue-with-template.yml5
-rw-r--r--changelogs/unreleased/fix-docs-help-shortcut.yml5
-rw-r--r--changelogs/unreleased/fix-gb-fix-import-export-restoring-associations.yml6
-rw-r--r--changelogs/unreleased/fix-gb-improve-manual-action-tooltips.yml5
-rw-r--r--changelogs/unreleased/fix-last-push-event-widget-layout.yml5
-rw-r--r--changelogs/unreleased/fix-move-2fa-disable-button.yml5
-rw-r--r--changelogs/unreleased/fix-onion-skin-reenter.yml5
-rw-r--r--changelogs/unreleased/fix-profile-settings-content-width.yml5
-rw-r--r--changelogs/unreleased/fix-profile-settings-sidebar-heading.yml5
-rw-r--r--changelogs/unreleased/fix-redirect-routes-schema.yml (renamed from changelogs/unreleased/16117-improve-search-for-issues.yml)2
-rw-r--r--changelogs/unreleased/fix-remove-unnecessary-sidebar-element-alignment.yml5
-rw-r--r--changelogs/unreleased/fix_build_count_in_pipeline_success_maild.yml5
-rw-r--r--changelogs/unreleased/fix_gitlab-ce-41891.yml5
-rw-r--r--changelogs/unreleased/fj-40053-error-500-members-list.yml5
-rw-r--r--changelogs/unreleased/fj-40279-normalize-ldap-dn-api.yml5
-rw-r--r--changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml5
-rw-r--r--changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml5
-rw-r--r--changelogs/unreleased/gitaly-git-http-ssh.yml6
-rw-r--r--changelogs/unreleased/index-namespaces-lower-name.yml5
-rw-r--r--changelogs/unreleased/issue-description-field-typo.yml5
-rw-r--r--changelogs/unreleased/issue_37143_2.yml5
-rw-r--r--changelogs/unreleased/issue_40500.yml5
-rw-r--r--changelogs/unreleased/issue_41460.yml5
-rw-r--r--changelogs/unreleased/issues-40986-get-participants-from-issues-mr-api.yml5
-rw-r--r--changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml5
-rw-r--r--changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml5
-rw-r--r--changelogs/unreleased/jivl-fix-import-project-url-bug.yml5
-rw-r--r--changelogs/unreleased/jramsay-4012-i18n-compare.yml5
-rw-r--r--changelogs/unreleased/jramsay-41590-add-readme-case.yml5
-rw-r--r--changelogs/unreleased/ldap_username_attributes.yml5
-rw-r--r--changelogs/unreleased/lfs-badge.yml5
-rw-r--r--changelogs/unreleased/merge-request-target-branch-perf.yml5
-rw-r--r--changelogs/unreleased/mk-delete-orphaned-routes-before-validation.yml6
-rw-r--r--changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml6
-rw-r--r--changelogs/unreleased/multiple-clusters-single-list.yml5
-rw-r--r--changelogs/unreleased/optimize-issues-avoid-noop-empty-cache-updates2.yml6
-rw-r--r--changelogs/unreleased/osw-introduce-merge-request-statistics.yml5
-rw-r--r--changelogs/unreleased/remove-incorrect-guidance.yml6
-rw-r--r--changelogs/unreleased/remove-links-mr-empty-state.yml5
-rw-r--r--changelogs/unreleased/remove-soft-removals.yml5
-rw-r--r--changelogs/unreleased/remove-tabindexes-from-tag-form.yml5
-rw-r--r--changelogs/unreleased/sh-add-schedule-pipeline-run-now.yml5
-rw-r--r--changelogs/unreleased/sh-catch-invalid-uri-markdown.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bare-import-hooks.yml5
-rw-r--r--changelogs/unreleased/sh-log-when-user-blocked.yml5
-rw-r--r--changelogs/unreleased/sh-make-kib-human.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-commit-stats.yml5
-rw-r--r--changelogs/unreleased/sh-remove-shared-runners-and-more.yml5
-rw-r--r--changelogs/unreleased/sh-validate-path-project-import.yml5
-rw-r--r--changelogs/unreleased/show-inline-edit-btn.yml5
-rw-r--r--changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml5
-rw-r--r--changelogs/unreleased/sophie-h-gitlab-ce-patch-15.yml5
-rw-r--r--changelogs/unreleased/tc-correct-email-in-reply-to.yml5
-rw-r--r--changelogs/unreleased/update-redis-rack.yml5
-rw-r--r--changelogs/unreleased/winh-modal-target-id.yml5
-rw-r--r--changelogs/unreleased/winh-search-page-filters.yml5
-rw-r--r--changelogs/unreleased/winh-style-modals.yml5
-rw-r--r--changelogs/unreleased/winh-translate-contributors-page-dates.yml5
-rw-r--r--config/dependency_decisions.yml13
-rw-r--r--config/initializers/ar5_pg_10_support.rb57
-rw-r--r--config/initializers/warden.rb4
-rw-r--r--config/routes/project.rb1
-rw-r--r--config/webpack.config.js8
-rw-r--r--db/migrate/20170425112128_create_pipeline_schedules_table.rb2
-rw-r--r--db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb17
-rw-r--r--db/migrate/20171207185153_add_merge_request_state_index.rb18
-rw-r--r--db/migrate/20171211145425_add_can_push_to_deploy_keys_projects.rb15
-rw-r--r--db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb64
-rw-r--r--db/migrate/20180113220114_rework_redirect_routes_indexes.rb68
-rw-r--r--db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb63
-rw-r--r--db/post_migrate/20171215121259_remove_can_push_from_keys.rb17
-rw-r--r--db/schema.rb8
-rw-r--r--doc/README.md8
-rw-r--r--doc/administration/auth/okta.md2
-rw-r--r--doc/administration/container_registry.md2
-rw-r--r--doc/administration/high_availability/redis.md2
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md8
-rw-r--r--doc/api/award_emoji.md6
-rw-r--r--doc/api/deploy_keys.md48
-rw-r--r--doc/api/deployments.md2
-rw-r--r--doc/api/group_milestones.md2
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/milestones.md15
-rw-r--r--doc/api/notes.md2
-rw-r--r--doc/api/project_snippets.md9
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/api/system_hooks.md3
-rw-r--r--doc/api/users.md4
-rw-r--r--doc/articles/index.md101
-rw-r--r--doc/articles/numerous_undo_possibilities_in_git/index.md498
-rw-r--r--doc/articles/runner_autoscale_aws/index.md411
-rw-r--r--doc/ci/README.md159
-rw-r--r--doc/ci/autodeploy/index.md2
-rw-r--r--doc/ci/environments.md2
-rw-r--r--doc/ci/examples/README.md101
-rw-r--r--doc/ci/examples/dast.md40
-rw-r--r--doc/ci/examples/php.md4
-rw-r--r--doc/ci/examples/sast_docker.md55
-rw-r--r--doc/ci/examples/test-and-deploy-python-application-to-heroku.md2
-rw-r--r--doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md5
-rw-r--r--doc/ci/examples/test-phoenix-application.md1
-rw-r--r--doc/ci/quick_start/README.md6
-rw-r--r--doc/ci/runners/README.md22
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/ci/yaml/README.md4
-rw-r--r--doc/development/automatic_ce_ee_merge.md2
-rw-r--r--doc/development/background_migrations.md2
-rw-r--r--doc/development/changelog.md4
-rw-r--r--doc/development/doc_styleguide.md42
-rw-r--r--doc/development/ee_features.md20
-rw-r--r--doc/development/fe_guide/axios.md2
-rw-r--r--doc/development/fe_guide/vue.md2
-rw-r--r--doc/development/migration_style_guide.md2
-rw-r--r--doc/development/performance.md3
-rw-r--r--doc/development/profiling.md45
-rw-r--r--doc/development/rake_tasks.md2
-rw-r--r--doc/development/testing_guide/best_practices.md1
-rw-r--r--doc/development/utilities.md45
-rw-r--r--doc/development/ux_guide/animation.md2
-rw-r--r--doc/development/ux_guide/components.md6
-rw-r--r--doc/development/ux_guide/copy.md2
-rw-r--r--doc/development/writing_documentation.md20
-rw-r--r--doc/install/README.md6
-rw-r--r--doc/install/azure/index.md4
-rw-r--r--doc/install/installation.md10
-rw-r--r--doc/install/openshift_and_gitlab/index.md2
-rw-r--r--doc/install/requirements.md3
-rw-r--r--doc/raketasks/backup_restore.md21
-rw-r--r--doc/system_hooks/system_hooks.md149
-rw-r--r--doc/topics/autodevops/index.md61
-rw-r--r--doc/topics/git/how_to_install_git/index.md2
-rw-r--r--doc/topics/git/index.md2
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/img/branching.png (renamed from doc/articles/numerous_undo_possibilities_in_git/img/branching.png)bin26245 -> 26245 bytes
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/img/rebase_reset.png (renamed from doc/articles/numerous_undo_possibilities_in_git/img/rebase_reset.png)bin43609 -> 43609 bytes
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/img/revert.png (renamed from doc/articles/numerous_undo_possibilities_in_git/img/revert.png)bin28112 -> 28112 bytes
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/index.md497
-rw-r--r--doc/university/glossary/README.md2
-rw-r--r--doc/university/high-availability/aws/README.md4
-rw-r--r--doc/update/10.3-to-10.4.md2
-rw-r--r--doc/user/admin_area/monitoring/img/convdev_index.pngbin116112 -> 119743 bytes
-rw-r--r--doc/user/index.md19
-rw-r--r--doc/user/profile/account/two_factor_authentication.md2
-rw-r--r--doc/user/project/clusters/index.md276
-rw-r--r--doc/user/project/integrations/emails_on_push.md2
-rw-r--r--doc/user/project/integrations/kubernetes.md10
-rw-r--r--doc/user/project/integrations/webhooks.md2
-rw-r--r--doc/user/project/issues/crosslinking_issues.md2
-rw-r--r--doc/user/project/milestones/index.md2
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md2
-rw-r--r--doc/user/project/pages/introduction.md2
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedule_play.pngbin0 -> 39142 bytes
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedules_list.pngbin14665 -> 38034 bytes
-rw-r--r--doc/user/project/pipelines/schedules.md15
-rw-r--r--doc/workflow/lfs/img/lfs-icon.pngbin0 -> 4317 bytes
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md5
-rw-r--r--doc/workflow/notifications.md32
-rw-r--r--features/project/issues/issues.feature2
-rw-r--r--features/steps/project/issues/issues.rb8
-rw-r--r--lib/api/circuit_breakers.rb4
-rw-r--r--lib/api/deploy_keys.rb65
-rw-r--r--lib/api/deployments.rb4
-rw-r--r--lib/api/entities.rb16
-rw-r--r--lib/api/helpers/common_helpers.rb6
-rw-r--r--lib/api/helpers/internal_helpers.rb15
-rw-r--r--lib/api/jobs.rb1
-rw-r--r--lib/api/members.rb2
-rw-r--r--lib/api/project_milestones.rb9
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/projects.rb4
-rw-r--r--lib/api/protected_branches.rb2
-rw-r--r--lib/api/services.rb2
-rw-r--r--lib/api/system_hooks.rb1
-rw-r--r--lib/api/v3/builds.rb1
-rw-r--r--lib/api/v3/deploy_keys.rb46
-rw-r--r--lib/api/v3/entities.rb5
-rw-r--r--lib/api/v3/members.rb2
-rw-r--r--lib/api/v3/services.rb2
-rw-r--r--lib/backup/manager.rb9
-rw-r--r--lib/banzai/filter/relative_link_filter.rb15
-rw-r--r--lib/banzai/filter/wiki_link_filter/rewriter.rb4
-rw-r--r--lib/gitlab/auth/blocked_user_tracker.rb36
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb4
-rw-r--r--lib/gitlab/background_migration/copy_column.rb2
-rw-r--r--lib/gitlab/background_migration/prepare_untracked_uploads.rb10
-rw-r--r--lib/gitlab/bare_repository_import/repository.rb10
-rw-r--r--lib/gitlab/ci/config/entry/validators.rb16
-rw-r--r--lib/gitlab/ci/pipeline/chain/skip.rb6
-rw-r--r--lib/gitlab/ci/stage/seed.rb6
-rw-r--r--lib/gitlab/ci/status/build/action.rb5
-rw-r--r--lib/gitlab/database/migration_helpers.rb5
-rw-r--r--lib/gitlab/git/blob.rb4
-rw-r--r--lib/gitlab/git/commit.rb55
-rw-r--r--lib/gitlab/git/conflict/resolver.rb2
-rw-r--r--lib/gitlab/git/gitlab_projects.rb64
-rw-r--r--lib/gitlab/git/ref.rb4
-rw-r--r--lib/gitlab/git/repository.rb172
-rw-r--r--lib/gitlab/git/rev_list.rb2
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb19
-rw-r--r--lib/gitlab/gitaly_client/conflict_files_stitcher.rb47
-rw-r--r--lib/gitlab/gitaly_client/conflicts_service.rb38
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb28
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb32
-rw-r--r--lib/gitlab/github_import/client.rb6
-rw-r--r--lib/gitlab/gpg/commit.rb8
-rw-r--r--lib/gitlab/import_export/file_importer.rb6
-rw-r--r--lib/gitlab/import_export/saver.rb2
-rw-r--r--lib/gitlab/import_export/shared.rb14
-rw-r--r--lib/gitlab/kubernetes/helm/install_command.rb6
-rw-r--r--lib/gitlab/kubernetes/helm/pod.rb39
-rw-r--r--lib/gitlab/o_auth.rb6
-rw-r--r--lib/gitlab/o_auth/user.rb11
-rw-r--r--lib/gitlab/performance_bar.rb1
-rw-r--r--lib/gitlab/profiler.rb142
-rw-r--r--lib/gitlab/project_search_results.rb13
-rw-r--r--lib/gitlab/regex.rb2
-rw-r--r--lib/gitlab/user_access.rb10
-rw-r--r--lib/gitlab/utils.rb4
-rw-r--r--lib/gitlab/utils/override.rb111
-rw-r--r--lib/gitlab/workhorse.rb5
-rw-r--r--lib/tasks/dev.rake5
-rw-r--r--lib/tasks/gitlab/check.rake2
-rw-r--r--lib/tasks/gitlab/shell.rake10
-rw-r--r--lib/tasks/lint.rake12
-rw-r--r--lib/tasks/migrate/setup_postgresql.rake2
-rw-r--r--package.json15
-rw-r--r--qa/README.md11
-rw-r--r--qa/qa/page/main/login.rb16
-rw-r--r--qa/qa/page/project/settings/common.rb4
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb19
-rw-r--r--qa/qa/page/project/settings/repository.rb11
-rw-r--r--rubocop/cop/gitlab/predicate_memoization.rb39
-rw-r--r--rubocop/rubocop.rb1
-rwxr-xr-xscripts/add-code-formatters18
-rw-r--r--scripts/pre-commit18
-rwxr-xr-xscripts/static-analysis5
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb7
-rw-r--r--spec/controllers/admin/hooks_controller_spec.rb6
-rw-r--r--spec/controllers/dashboard/groups_controller_spec.rb20
-rw-r--r--spec/controllers/groups/children_controller_spec.rb24
-rw-r--r--spec/controllers/import/gitlab_projects_controller_spec.rb38
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb75
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb8
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb8
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb26
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb66
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb42
-rw-r--r--spec/factories/deploy_keys_projects.rb4
-rw-r--r--spec/factories/keys.rb4
-rw-r--r--spec/factories/redirect_routes.rb15
-rw-r--r--spec/features/admin/admin_builds_spec.rb16
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb14
-rw-r--r--spec/features/admin/admin_hooks_spec.rb65
-rw-r--r--spec/features/boards/boards_spec.rb11
-rw-r--r--spec/features/boards/sidebar_spec.rb10
-rw-r--r--spec/features/copy_as_gfm_spec.rb2
-rw-r--r--spec/features/cycle_analytics_spec.rb1
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb9
-rw-r--r--spec/features/merge_request/user_assigns_themselves_spec.rb49
-rw-r--r--spec/features/merge_request/user_awards_emoji_spec.rb (renamed from spec/features/merge_requests/award_spec.rb)4
-rw-r--r--spec/features/merge_request/user_cherry_picks_spec.rb (renamed from spec/features/merge_requests/cherry_pick_spec.rb)22
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb (renamed from spec/features/merge_requests/image_diff_notes_spec.rb)39
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb31
-rw-r--r--spec/features/merge_request/user_customizes_merge_commit_message_spec.rb (renamed from spec/features/merge_requests/merge_commit_message_toggle_spec.rb)15
-rw-r--r--spec/features/merge_request/user_edits_mr_spec.rb11
-rw-r--r--spec/features/merge_request/user_locks_discussion_spec.rb (renamed from spec/features/merge_requests/discussion_lock_spec.rb)6
-rw-r--r--spec/features/merge_request/user_merges_immediately_spec.rb (renamed from spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb)22
-rw-r--r--spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb (renamed from spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb)51
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb (renamed from spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb)82
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb (renamed from spec/features/merge_requests/user_posts_diff_notes_spec.rb)15
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb (renamed from spec/features/merge_requests/user_posts_notes_spec.rb)8
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb (renamed from spec/features/merge_requests/conflicts_spec.rb)7
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb (renamed from spec/features/merge_requests/diff_notes_resolve_spec.rb)31
-rw-r--r--spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb (renamed from spec/features/merge_requests/resolve_outdated_diff_discussions.rb)2
-rw-r--r--spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb26
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb (renamed from spec/features/merge_requests/diff_notes_avatars_spec.rb)8
-rw-r--r--spec/features/merge_request/user_sees_closing_issues_message_spec.rb (renamed from spec/features/merge_requests/closes_issues_spec.rb)10
-rw-r--r--spec/features/merge_request/user_sees_deleted_target_branch_spec.rb22
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb56
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb (renamed from spec/features/merge_requests/diffs_spec.rb)4
-rw-r--r--spec/features/merge_request/user_sees_discussions_spec.rb (renamed from spec/features/merge_requests/discussion_spec.rb)19
-rw-r--r--spec/features/merge_request/user_sees_empty_state_spec.rb30
-rw-r--r--spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb (renamed from spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb)24
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb (renamed from spec/features/merge_requests/widget_spec.rb)4
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb (renamed from spec/features/merge_requests/mini_pipeline_graph_spec.rb)28
-rw-r--r--spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb24
-rw-r--r--spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb (renamed from spec/features/merge_requests/deleted_source_branch_spec.rb)20
-rw-r--r--spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb35
-rw-r--r--spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb34
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb (renamed from spec/features/merge_requests/pipelines_spec.rb)32
-rw-r--r--spec/features/merge_request/user_sees_system_notes_spec.rb (renamed from spec/features/merge_requests/user_sees_system_notes_spec.rb)6
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb (renamed from spec/features/merge_requests/versions_spec.rb)45
-rw-r--r--spec/features/merge_request/user_sees_wip_help_message_spec.rb (renamed from spec/features/merge_requests/wip_message_spec.rb)8
-rw-r--r--spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb (renamed from spec/features/merge_requests/create_new_mr_spec.rb)9
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb (renamed from spec/features/merge_requests/toggle_whitespace_changes_spec.rb)13
-rw-r--r--spec/features/merge_request/user_uses_slash_commands_spec.rb (renamed from spec/features/merge_requests/user_uses_slash_commands_spec.rb)19
-rw-r--r--spec/features/merge_requests/assign_issues_spec.rb51
-rw-r--r--spec/features/merge_requests/create_new_mr_from_fork_spec.rb89
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb94
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb73
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb93
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb97
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb337
-rw-r--r--spec/features/merge_requests/filters_generic_behavior_spec.rb81
-rw-r--r--spec/features/merge_requests/form_spec.rb301
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb136
-rw-r--r--spec/features/merge_requests/target_branch_spec.rb33
-rw-r--r--spec/features/merge_requests/toggler_behavior_spec.rb28
-rw-r--r--spec/features/merge_requests/user_filters_by_assignees_spec.rb36
-rw-r--r--spec/features/merge_requests/user_filters_by_labels_spec.rb49
-rw-r--r--spec/features/merge_requests/user_filters_by_milestones_spec.rb62
-rw-r--r--spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb38
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb4
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb (renamed from spec/features/merge_requests/update_merge_requests_spec.rb)20
-rw-r--r--spec/features/merge_requests/widget_deployments_spec.rb59
-rw-r--r--spec/features/oauth_login_spec.rb3
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb12
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb14
-rw-r--r--spec/features/projects/merge_requests/list_spec.rb44
-rw-r--r--spec/features/projects/merge_requests/user_manages_subscription_spec.rb20
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb6
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb26
-rw-r--r--spec/finders/group_descendants_finder_spec.rb20
-rw-r--r--spec/finders/milestones_finder_spec.rb41
-rw-r--r--spec/helpers/blob_helper_spec.rb7
-rw-r--r--spec/helpers/issuables_helper_spec.rb35
-rw-r--r--spec/javascripts/boards/list_spec.js2
-rw-r--r--spec/javascripts/boards/mock_data.js1
-rw-r--r--spec/javascripts/boards/utils/query_data_spec.js27
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js1
-rw-r--r--spec/javascripts/commit_merge_requests_spec.js60
-rw-r--r--spec/javascripts/create_item_dropdown_spec.js106
-rw-r--r--spec/javascripts/deploy_keys/components/app_spec.js1
-rw-r--r--spec/javascripts/deploy_keys/components/key_spec.js20
-rw-r--r--spec/javascripts/environments/environments_app_spec.js1
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js1
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js1
-rw-r--r--spec/javascripts/fixtures/create_item_dropdown.html.haml13
-rw-r--r--spec/javascripts/fixtures/search.rb18
-rw-r--r--spec/javascripts/flash_spec.js12
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js33
-rw-r--r--spec/javascripts/issue_show/components/fields/description_template_spec.js2
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js2
-rw-r--r--spec/javascripts/job_spec.js5
-rw-r--r--spec/javascripts/jobs/header_spec.js34
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js6
-rw-r--r--spec/javascripts/merge_request_notes_spec.js1
-rw-r--r--spec/javascripts/merge_request_spec.js7
-rw-r--r--spec/javascripts/notebook/cells/markdown_spec.js12
-rw-r--r--spec/javascripts/notebook/cells/output/html_sanitize_tests.js66
-rw-r--r--spec/javascripts/notebook/cells/output/html_spec.js29
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js10
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js1
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js9
-rw-r--r--spec/javascripts/notes/components/noteable_note_spec.js2
-rw-r--r--spec/javascripts/notes_spec.js1
-rw-r--r--spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js63
-rw-r--r--spec/javascripts/pipelines/async_button_spec.js6
-rw-r--r--spec/javascripts/pipelines/pipeline_details_mediator_spec.js1
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js1
-rw-r--r--spec/javascripts/pipelines/stage_spec.js1
-rw-r--r--spec/javascripts/registry/components/app_spec.js1
-rw-r--r--spec/javascripts/repo/stores/actions_spec.js13
-rw-r--r--spec/javascripts/search_spec.js40
-rw-r--r--spec/javascripts/sidebar/sidebar_assignees_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_mediator_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_move_issue_spec.js1
-rw-r--r--spec/javascripts/sidebar/subscriptions_spec.js12
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js7
-rw-r--r--spec/javascripts/smart_interval_spec.js1
-rw-r--r--spec/javascripts/test_bundle.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js37
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js57
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js53
-rw-r--r--spec/javascripts/vue_shared/components/loading_icon_spec.js3
-rw-r--r--spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js77
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js1
-rw-r--r--spec/javascripts/zen_mode_spec.js2
-rw-r--r--spec/lib/backup/manager_spec.rb6
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb65
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb8
-rw-r--r--spec/lib/gitlab/auth/blocked_user_tracker_spec.rb53
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb10
-rw-r--r--spec/lib/gitlab/auth_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/config/entry/key_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/status/build/action_spec.rb8
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb12
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb78
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb56
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb46
-rw-r--r--spec/lib/gitlab/git/rev_list_spec.rb11
-rw-r--r--spec/lib/gitlab/git_access_spec.rb14
-rw-r--r--spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb54
-rw-r--r--spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb33
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb24
-rw-r--r--spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb57
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb19
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb10
-rw-r--r--spec/lib/gitlab/profiler_spec.rb156
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb39
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb158
-rw-r--r--spec/lib/gitlab/utils_spec.rb16
-rw-r--r--spec/mailers/notify_spec.rb49
-rw-r--r--spec/migrations/fix_wrongly_renamed_routes_spec.rb65
-rw-r--r--spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb43
-rw-r--r--spec/migrations/remove_empty_fork_networks_spec.rb13
-rw-r--r--spec/migrations/update_upload_paths_to_system_spec.rb58
-rw-r--r--spec/models/ci/build_spec.rb7
-rw-r--r--spec/models/ci/pipeline_spec.rb7
-rw-r--r--spec/models/commit_range_spec.rb6
-rw-r--r--spec/models/commit_spec.rb37
-rw-r--r--spec/models/concerns/triggerable_hooks_spec.rb43
-rw-r--r--spec/models/deploy_keys_project_spec.rb2
-rw-r--r--spec/models/hooks/system_hook_spec.rb3
-rw-r--r--spec/models/hooks/web_hook_spec.rb6
-rw-r--r--spec/models/merge_request_diff_spec.rb22
-rw-r--r--spec/models/merge_request_spec.rb162
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb4
-rw-r--r--spec/models/project_spec.rb19
-rw-r--r--spec/models/project_statistics_spec.rb6
-rw-r--r--spec/models/push_event_spec.rb26
-rw-r--r--spec/models/repository_spec.rb52
-rw-r--r--spec/models/route_spec.rb121
-rw-r--r--spec/models/service_spec.rb34
-rw-r--r--spec/models/user_spec.rb16
-rw-r--r--spec/requests/api/deploy_keys_spec.rb12
-rw-r--r--spec/requests/api/deployments_spec.rb54
-rw-r--r--spec/requests/api/internal_spec.rb41
-rw-r--r--spec/requests/api/issues_spec.rb9
-rw-r--r--spec/requests/api/jobs_spec.rb27
-rw-r--r--spec/requests/api/members_spec.rb10
-rw-r--r--spec/requests/api/merge_requests_spec.rb26
-rw-r--r--spec/requests/api/project_milestones_spec.rb40
-rw-r--r--spec/requests/api/project_snippets_spec.rb13
-rw-r--r--spec/requests/api/projects_spec.rb26
-rw-r--r--spec/requests/api/protected_branches_spec.rb6
-rw-r--r--spec/requests/api/services_spec.rb4
-rw-r--r--spec/requests/api/system_hooks_spec.rb20
-rw-r--r--spec/requests/api/v3/builds_spec.rb27
-rw-r--r--spec/requests/api/v3/deploy_keys_spec.rb2
-rw-r--r--spec/requests/api/v3/members_spec.rb10
-rw-r--r--spec/requests/api/v3/merge_requests_spec.rb26
-rw-r--r--spec/requests/lfs_http_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/predicate_memoization_spec.rb100
-rw-r--r--spec/serializers/deploy_key_entity_spec.rb15
-rw-r--r--spec/services/labels/promote_service_spec.rb13
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb19
-rw-r--r--spec/services/merge_requests/create_service_spec.rb61
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb94
-rw-r--r--spec/services/notification_service_spec.rb62
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb15
-rw-r--r--spec/services/projects/gitlab_projects_import_service_spec.rb31
-rw-r--r--spec/services/system_hooks_service_spec.rb13
-rw-r--r--spec/services/system_note_service_spec.rb2
-rw-r--r--spec/services/test_hooks/system_service_spec.rb20
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/support/devise_helpers.rb15
-rw-r--r--spec/support/email_helpers.rb4
-rw-r--r--spec/support/login_helpers.rb7
-rw-r--r--spec/support/services_shared_context.rb8
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb99
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb140
-rw-r--r--spec/support/slack_mattermost_notifications_shared_examples.rb1
-rw-r--r--spec/support/test_env.rb8
-rw-r--r--spec/workers/new_issue_worker_spec.rb5
-rw-r--r--spec/workers/new_merge_request_worker_spec.rb6
-rw-r--r--spec/workers/repository_fork_worker_spec.rb8
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml72
-rw-r--r--vendor/prometheus/values.yaml205
-rw-r--r--yarn.lock1523
924 files changed, 12323 insertions, 6605 deletions
diff --git a/.eslintrc b/.eslintrc
index 6dbe269e594..ad5eaebccae 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -10,7 +10,6 @@
],
"globals": {
"__webpack_public_path__": true,
- "_": false,
"gl": false,
"gon": false,
"localStorage": false
diff --git a/.gitignore b/.gitignore
index 4933575332b..2004c2a09b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
*.swp
*.mo
*.edit.po
+*.rej
.DS_Store
.bundle
.chef
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f038ce72aeb..f9c5ebe7a35 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -61,6 +61,9 @@ stages:
.use-pg: &use-pg
services:
+ # As of Jan 2018, we don't have a strong reason to upgrade to 9.6 for CI yet,
+ # so using the least common denominator ensures backwards compatibility
+ # (as many users are still using 9.2).
- postgres:9.2
- redis:alpine
@@ -287,7 +290,7 @@ flaky-examples-check:
- scripts/merge-reports ${NEW_FLAKY_SPECS_REPORT} rspec_flaky/new_*_*.json
- scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
-setup-test-env:
+compile-assets:
<<: *dedicated-runner
<<: *except-docs
<<: *use-pg
@@ -298,13 +301,25 @@ setup-test-env:
- node --version
- yarn install --frozen-lockfile --cache-folder .yarn-cache
- bundle exec rake gitlab:assets:compile
- - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
- - scripts/gitaly-test-build # Do not use 'bundle exec' here
artifacts:
expire_in: 7d
paths:
- node_modules
- public/assets
+
+setup-test-env:
+ <<: *dedicated-runner
+ <<: *except-docs
+ <<: *use-pg
+ stage: prepare
+ cache:
+ <<: *default-cache
+ script:
+ - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
+ - scripts/gitaly-test-build # Do not use 'bundle exec' here
+ artifacts:
+ expire_in: 7d
+ paths:
- tmp/tests
rspec-pg 0 26: *rspec-metadata-pg
@@ -661,6 +676,7 @@ lint:javascript:report:
<<: *pull-cache
stage: post-test
dependencies:
+ - compile-assets
- setup-test-env
before_script: []
script:
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
index 9b541aadad1..102eb7e7953 100644
--- a/.gitlab/merge_request_templates/Documentation.md
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -11,4 +11,6 @@ See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#ch
- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location.
- [ ] Make sure internal links pointing to the document in question are not broken.
- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory.
+- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/doc_styleguide.html#redirections-for-pages-with-disqus-comments) to the new document if there are any Disqus comments on the old document thread.
- [ ] If working on CE, submit an MR to EE with the changes as well.
+- [ ] Ping one of the technical writers for review.
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 085dc153596..8d2276f71be 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,27 +1,26 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
-# on 2017-12-14 12:04:26 +0100 using RuboCop version 0.52.0.
+# on 2018-01-18 18:23:26 +0100 using RuboCop version 0.52.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 174
+# Offense count: 181
Capybara/CurrentPathExpectation:
Enabled: false
-# Offense count: 951
+# Offense count: 956
Capybara/FeatureMethods:
Enabled: false
-# Offense count: 24
+# Offense count: 23
FactoryBot/DynamicAttributeDefinedStatically:
Exclude:
- 'spec/factories/broadcast_messages.rb'
- 'spec/factories/ci/builds.rb'
- 'spec/factories/ci/runners.rb'
- 'spec/factories/clusters/applications/helm.rb'
- - 'spec/factories/clusters/applications/ingress.rb'
- 'spec/factories/clusters/platforms/kubernetes.rb'
- 'spec/factories/emails.rb'
- 'spec/factories/gpg_keys.rb'
@@ -33,40 +32,31 @@ FactoryBot/DynamicAttributeDefinedStatically:
- 'spec/factories/todos.rb'
- 'spec/factories/uploads.rb'
-# Offense count: 65
+# Offense count: 167
# Cop supports --auto-correct.
Layout/EmptyLinesAroundArguments:
Enabled: false
-# Offense count: 249
+# Offense count: 253
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
Layout/ExtraSpacing:
Enabled: false
-# Offense count: 82
+# Offense count: 83
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Layout/IndentArray:
Enabled: false
-# Offense count: 239
+# Offense count: 237
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_braces
Layout/IndentHash:
Enabled: false
-# Offense count: 15
-# Cop supports --auto-correct.
-# Configuration parameters: .
-# SupportedStyles: space, no_space
-# SupportedStylesForEmptyBraces: space, no_space
-Layout/SpaceBeforeBlockBraces:
- EnforcedStyle: space
- EnforcedStyleForEmptyBraces: space
-
# Offense count: 11
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment.
@@ -97,7 +87,7 @@ Layout/SpaceInsideArrayLiteralBrackets:
Exclude:
- 'spec/lib/gitlab/import_export/relation_factory_spec.rb'
-# Offense count: 323
+# Offense count: 327
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
# SupportedStyles: space, no_space
@@ -105,7 +95,7 @@ Layout/SpaceInsideArrayLiteralBrackets:
Layout/SpaceInsideBlockBraces:
Enabled: false
-# Offense count: 146
+# Offense count: 156
# Cop supports --auto-correct.
Layout/SpaceInsideParens:
Enabled: false
@@ -118,7 +108,7 @@ Layout/SpaceInsidePercentLiteralDelimiters:
- 'lib/gitlab/health_checks/fs_shards_check.rb'
- 'spec/lib/gitlab/health_checks/fs_shards_check_spec.rb'
-# Offense count: 25
+# Offense count: 26
Lint/DuplicateMethods:
Exclude:
- 'app/models/application_setting.rb'
@@ -144,7 +134,7 @@ Lint/InterpolationCheck:
- 'spec/features/users_spec.rb'
- 'spec/services/quick_actions/interpret_service_spec.rb'
-# Offense count: 198
+# Offense count: 206
# Configuration parameters: MaximumRangeSize.
Lint/MissingCopEnableDirective:
Enabled: false
@@ -185,6 +175,12 @@ Lint/UriEscapeUnescape:
- 'spec/requests/api/issues_spec.rb'
- 'spec/requests/api/v3/issues_spec.rb'
+# Offense count: 1
+# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
+# URISchemes: http, https
+Metrics/LineLength:
+ Max: 1310
+
# Offense count: 2
Naming/ConstantName:
Exclude:
@@ -202,13 +198,13 @@ Naming/HeredocDelimiterCase:
- 'spec/support/repo_helpers.rb'
- 'spec/support/seed_repo.rb'
-# Offense count: 101
+# Offense count: 112
# Configuration parameters: Blacklist.
# Blacklist: END, (?-mix:EO[A-Z]{1})
Naming/HeredocDelimiterNaming:
Enabled: false
-# Offense count: 28
+# Offense count: 27
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect.
Performance/HashEachMethods:
@@ -225,21 +221,27 @@ Performance/UriDefaultParser:
Exclude:
- 'lib/gitlab/url_sanitizer.rb'
-# Offense count: 3745
+# Offense count: 3821
# Configuration parameters: Prefixes.
# Prefixes: when, with, without
RSpec/ContextWording:
Enabled: false
-# Offense count: 291
+# Offense count: 293
RSpec/EmptyLineAfterFinalLet:
Enabled: false
-# Offense count: 180
+# Offense count: 188
RSpec/EmptyLineAfterSubject:
Enabled: false
-# Offense count: 220
+# Offense count: 258
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: method_call, block
+RSpec/ExpectChange:
+ Enabled: false
+
+# Offense count: 221
RSpec/ExpectInHook:
Enabled: false
@@ -304,7 +306,7 @@ RSpec/OverwritingSetup:
- 'spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb'
- 'spec/services/notes/quick_actions_service_spec.rb'
-# Offense count: 917
+# Offense count: 965
# Configuration parameters: Strict, EnforcedStyle.
# SupportedStyles: inflected, explicit
RSpec/PredicateMatcher:
@@ -314,13 +316,13 @@ RSpec/PredicateMatcher:
RSpec/RepeatedExample:
Enabled: false
-# Offense count: 132
+# Offense count: 140
# Configuration parameters: EnforcedStyle.
# SupportedStyles: and_return, block
RSpec/ReturnFromStub:
Enabled: false
-# Offense count: 105
+# Offense count: 112
RSpec/ScatteredLet:
Enabled: false
@@ -353,23 +355,23 @@ RSpec/VoidExpect:
- 'spec/models/ci/runner_spec.rb'
- 'spec/services/users/destroy_service_spec.rb'
-# Offense count: 40
+# Offense count: 41
# Configuration parameters: Include.
# Include: db/migrate/*.rb
Rails/CreateTableWithTimestamps:
Enabled: false
-# Offense count: 149
+# Offense count: 155
Rails/FilePath:
Enabled: false
-# Offense count: 119
+# Offense count: 121
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasManyOrHasOneDependent:
Enabled: false
-# Offense count: 113
+# Offense count: 157
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/InverseOf:
@@ -399,12 +401,6 @@ Rails/Presence:
- 'lib/gitlab/git/hook.rb'
- 'lib/gitlab/github_import/importer/releases_importer.rb'
-# Offense count: 14
-# Cop supports --auto-correct.
-Rails/RedundantReceiverInWithOptions:
- Exclude:
- - 'config/initializers/doorkeeper_openid_connect.rb'
-
# Offense count: 2
# Configuration parameters: Include.
# Include: db/migrate/*.rb
@@ -412,7 +408,7 @@ Rails/ReversibleMigration:
Exclude:
- 'db/migrate/20160824103857_drop_unused_ci_tables.rb'
-# Offense count: 430
+# Offense count: 446
# Configuration parameters: Blacklist.
# Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters
Rails/SkipsModelValidations:
@@ -439,7 +435,7 @@ Security/YAMLLoad:
- 'spec/models/clusters/platforms/kubernetes_spec.rb'
- 'spec/models/project_services/kubernetes_service_spec.rb'
-# Offense count: 63
+# Offense count: 64
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: percent_q, bare_percent
@@ -506,7 +502,7 @@ Style/EmptyLiteral:
- 'spec/requests/api/jobs_spec.rb'
- 'spec/support/chat_slash_commands_shared_examples.rb'
-# Offense count: 98
+# Offense count: 102
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, expanded
@@ -523,35 +519,28 @@ Style/EvalWithLocation:
Exclude:
- 'app/models/service.rb'
-# Offense count: 52
-# Cop supports --auto-correct.
-# Configuration parameters: Autocorrect, EnforcedStyle.
-# SupportedStyles: module_function, extend_self
-Style/ExtendSelf:
- Enabled: false
-
-# Offense count: 34
+# Offense count: 35
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: format, sprintf, percent
Style/FormatString:
Enabled: false
-# Offense count: 371
+# Offense count: 384
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Enabled: false
-# Offense count: 21
+# Offense count: 22
Style/IfInsideElse:
Enabled: false
-# Offense count: 781
+# Offense count: 809
# Cop supports --auto-correct.
Style/IfUnlessModifier:
Enabled: false
-# Offense count: 71
+# Offense count: 75
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: line_count_dependent, lambda, literal
@@ -573,7 +562,7 @@ Style/LineEndConcatenation:
Style/MethodCallWithoutArgsParentheses:
Enabled: false
-# Offense count: 17
+# Offense count: 18
Style/MethodMissing:
Enabled: false
@@ -599,28 +588,28 @@ Style/MultilineIfModifier:
- 'lib/api/commit_statuses.rb'
- 'lib/gitlab/ci/trace.rb'
-# Offense count: 23
+# Offense count: 25
# Cop supports --auto-correct.
# Configuration parameters: Whitelist.
# Whitelist: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with
Style/NestedParenthesizedCalls:
Enabled: false
-# Offense count: 20
+# Offense count: 19
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Enabled: false
-# Offense count: 58
+# Offense count: 61
# Cop supports --auto-correct.
# Configuration parameters: EnforcedOctalStyle.
# SupportedOctalStyles: zero_with_o, zero_only
Style/NumericLiteralPrefix:
Enabled: false
-# Offense count: 112
+# Offense count: 114
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle.
# SupportedStyles: predicate, comparison
@@ -641,7 +630,7 @@ Style/OrAssignment:
Style/ParallelAssignment:
Enabled: false
-# Offense count: 891
+# Offense count: 917
# Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
@@ -663,14 +652,14 @@ Style/PerlBackrefs:
- 'lib/gitlab/search_results.rb'
- 'lib/gitlab/sherlock/query.rb'
-# Offense count: 82
+# Offense count: 87
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, exploded
Style/RaiseArgs:
Enabled: false
-# Offense count: 8
+# Offense count: 9
# Cop supports --auto-correct.
Style/RedundantBegin:
Exclude:
@@ -689,7 +678,7 @@ Style/RedundantConditional:
Exclude:
- 'lib/system_check/helpers.rb'
-# Offense count: 58
+# Offense count: 57
# Cop supports --auto-correct.
Style/RedundantFreeze:
Enabled: false
@@ -709,31 +698,31 @@ Style/RedundantReturn:
- 'lib/gitlab/utils.rb'
- 'lib/google_api/auth.rb'
-# Offense count: 454
+# Offense count: 460
# Cop supports --auto-correct.
Style/RedundantSelf:
Enabled: false
-# Offense count: 140
+# Offense count: 142
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Enabled: false
-# Offense count: 35
+# Offense count: 36
# Cop supports --auto-correct.
Style/RescueModifier:
Enabled: false
-# Offense count: 105
+# Offense count: 107
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: implicit, explicit
Style/RescueStandardError:
Enabled: false
-# Offense count: 91
+# Offense count: 92
# Cop supports --auto-correct.
# Configuration parameters: ConvertCodeThatCanStartToReturnNil.
Style/SafeNavigation:
@@ -778,7 +767,7 @@ Style/StderrPuts:
Style/StringLiteralsInInterpolation:
Enabled: false
-# Offense count: 99
+# Offense count: 106
# Cop supports --auto-correct.
# Configuration parameters: IgnoredMethods.
# IgnoredMethods: respond_to, define_method
@@ -837,7 +826,7 @@ Style/UnlessElse:
- 'lib/tasks/gitlab/check.rake'
- 'spec/features/issues/award_emoji_spec.rb'
-# Offense count: 30
+# Offense count: 31
# Cop supports --auto-correct.
Style/UnneededInterpolation:
Enabled: false
@@ -856,7 +845,7 @@ Style/ZeroLengthPredicate:
- 'lib/extracts_path.rb'
- 'lib/gitlab/git/repository.rb'
-# Offense count: 22050
+# Offense count: 22840
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26580e7183f..e638a322d01 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,187 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.4.0 (2018-01-22)
+
+### Security (8 changes, 1 of them is from the community)
+
+- Upgrade Ruby to 2.3.6 to include security patches. !16016
+- Prevent a SQL injection in the MilestonesFinder.
+- Check user authorization for source and target projects when creating a merge request.
+- Fix path traversal in gitlab-ci.yml cache:key.
+- Fix writable shared deploy keys.
+- Filter out sensitive fields from the project services API. (Robert Schilling)
+- Fix RCE via project import mechanism.
+- Prevent OAuth login POST requests when a provider has been disabled.
+
+### Fixed (68 changes, 24 of them are from the community)
+
+- Update comment on image cursor and icons. !15760
+- Fixes the wording of headers in system info page. !15802 (Gilbert Roulot)
+- Reset todo counters when the target is deleted. !15807
+- Execute quick actions (if present) when creating MR from issue. !15810
+- fix build count in pipeline success mail. !15827 (Christiaan Van den Poel)
+- Fix error that was preventing users to change the access level of access requests for Groups or Projects. !15832
+- Last push event widget width for fixed layout. !15862 (George Tsiolis)
+- Hide link to issues/MRs from labels list if issues/MRs are disabled. !15863 (Sophie Herold)
+- Use relative URL for projects to avoid storing domains. !15876
+- Fix gitlab-rake gitlab:import:repos import schedule. !15931
+- Removed incorrect guidance stating blocked users will be removed from groups and project as members. !15947 (CesarApodaca)
+- Fix some POST/DELETE requests in IE by switching some bundles to Axios for Ajax requests. !15951
+- Fixing error 500 when member exist but not the user. !15970
+- show None when issue is in closed list and no labels assigned. !15976 (Christiaan Van den Poel)
+- Fix tags in the Activity tab not being clickable. !15996 (Mario de la Ossa)
+- Disable Vue pagination when only one page of content is available. !15999 (Mario de la Ossa)
+- disables shortcut to issue boards when issues are not enabled. !16020 (Christiaan Van den Poel)
+- Ignore lost+found folder during backup on a volume. !16036 (Julien Millau)
+- Fix abuse reports link url in admin area navbar. !16068 (megos)
+- Keep typographic hierarchy in User Settings. !16090 (George Tsiolis)
+- Adjust content width for User Settings, GPG Keys. !16093 (George Tsiolis)
+- Fix gitlab-rake gitlab:import:repos import schedule. !16115
+- Fix import project url not updating project name. !16120
+- Fix activity inline event line height on mobile. !16121 (George Tsiolis)
+- Fix slash commands dropdown description mis-alignment on Firefox. !16125 (Maurizio De Santis)
+- Remove unnecessary sidebar element realignment. !16159 (George Tsiolis)
+- User#projects_limit remove DB default and added NOT NULL constraint. !16165 (Mario de la Ossa)
+- Fix API endpoints to edit wiki pages where project belongs to a group. !16170
+- Fix breadcrumbs in User Settings. !16172 (rfwatson)
+- Move 2FA disable button. !16177 (George Tsiolis)
+- Fixing bug when wiki last version. !16197
+- Protected branch is now created for default branch on import. !16198
+- Prevent excessive DB load due to faulty DeleteConflictingRedirectRoutes background migration. !16205
+- Force Auto DevOps kubectl version to 1.8.6. !16218
+- Fix missing references to pipeline objects when restoring project with import/export feature. !16221
+- Fix inconsistent downcase of filenames in prefilled `Add` commit messages. !16232 (James Ramsay)
+- Default merge request title is set correctly again when external issue tracker is activated. !16356 (Ben305)
+- Ensure that emails contain absolute, rather than relative, links to user uploads. !16364
+- Prevent invalid Route path if path is unchanged. !16397
+- Fixing rack request mime type when using rack attack. !16427
+- Prevent RevList failing on non utf8 paths. !16440
+- Fix giant fork icons on forks page. !16474
+- Fix links to uploaded files on wiki pages. !16499
+- Modify `LDAP::Person` to return username value based on attributes.
+- Fixed merge request status badge not updating after merging.
+- Remove related links in MR widget when empty state.
+- Gracefully handle garbled URIs in Markdown.
+- Fix hooks not being set up properly for bare import Rake task.
+- Fix Mermaid drawings not loading on some browsers.
+- Humanize the units of "Showing last X KiB of log" in job trace.
+- Avoid leaving a push event empty if payload cannot be created.
+- Show authored date rather than committed date on the commit list.
+- Fix when branch creation fails don't post system note. (Mateusz Bajorski)
+- Fix viewing merge request diffs where the underlying blobs are unavailable.
+- Fix 500 error when visiting a commit where the blobs do not exist.
+- Set target_branch to the ref branch when creating MR from issue.
+- Fix closed text for issues on Todos page.
+- [API] Fix creating issue when assignee_id is empty.
+- Fix false positive issue references in merge requests caused by header anchor links.
+- Fixed chanages dropdown ellipsis positioning.
+- Fix shortcut links on help page.
+- Clears visual token on second backspace. (Martin Wortschack)
+- Fix onion-skin re-entering state.
+- fix button alignment on MWPS component.
+- Add optional search param for Merge Requests API.
+- Normalizing Identity extern_uid when saving the record.
+- Fixed typo for issue description field declaration. (Marcus Amargi)
+- Fix ANSI 256 bold colors in pipelines job output.
+
+### Changed (18 changes, 3 of them are from the community)
+
+- Make mail notifications of discussion notes In-Reply-To of each other. !14289
+- Migrate existing data from KubernetesService to Clusters::Platforms::Kubernetes. !15589
+- Implement checking GCP project billing status in cluster creation form. !15665
+- Present multiple clusters in a single list instead of a tabbed view. !15669
+- Remove soft removals related code. !15789
+- Only mark import and fork jobs as failed once all Sidekiq retries get exhausted. !15844
+- Translate date ranges on contributors page. !15846
+- Update issuable status icons. !15898
+- Update feature toggle design to use icons and make it i18n friendly. !15904
+- Update groups tree to use GitLab SVG icons, add last updated at information for projects. !15980
+- Allow forking a public project to a private group. !16050
+- Expose project_id on /api/v4/pages/domains. !16200 (Luc Didry)
+- Display graph values on hover within monitoring page. !16261
+- removed tabindexes from tag form. (Marcus Amargi)
+- Move edit button to second row on issue page (and change it to a pencil icon).
+- Run background migrations with a minimum interval.
+- Provide additional cookies to JIRA service requests to allow Oracle WebGates Basic Auth. (Stanislaw Wozniak)
+- Hide markdown toolbar in preview mode.
+
+### Performance (11 changes)
+
+- Improve the performance for counting diverging commits. Show 999+ if it is more than 1000 commits. !15963
+- Treat empty markdown and html strings as valid cached text, not missing cache that needs to be updated.
+- Cache merged and closed events data in merge_request_metrics table.
+- Speed up generation of commit stats by using Rugged native methods.
+- Improve search query for issues.
+- Improve search query for merge requests.
+- Eager load event target authors whenever possible.
+- Use simple Next/Prev paging for jobs to avoid large count queries on arbitrarily large sets of historical jobs.
+- Improve performance of MR discussions on large diffs.
+- Add index on namespaces lower(name) for UsersController#exists.
+- Fix timeout when filtering issues by label.
+
+### Added (26 changes, 8 of them are from the community)
+
+- Support new chat notifications parameters in Services API. !11435
+- Add online and status attribute to runner api entity. !11750
+- Adds ordering to projects contributors in API. !15469 (Jacopo Beschi @jacopo-beschi)
+- Add assets_sync gem to Gemfile. !15734
+- Add a gitlab:tcp_check rake task. !15759
+- add support for sorting in tags api. !15772 (haseebeqx)
+- Add Prometheus to available Cluster applications. !15895
+- Validate file status when commiting multiple files. !15922
+- List of avatars should never show +1. !15972 (Jacopo Beschi @jacopo-beschi)
+- Do not generate NPM links for private NPM modules in blob view. !16002 (Mario de la Ossa)
+- Backport fast database lookup of SSH authorized_keys from EE. !16014
+- Add i18n helpers to branch comparison view. !16031 (James Ramsay)
+- Add pause/resume button to project runners. !16032 (Mario de la Ossa)
+- Added option to user preferences to enable the multi file editor. !16056
+- Implement project jobs cache reset. !16067
+- Rendering of emoji's in Group-Overview. !16098 (Jacopo Beschi @jacopo-beschi)
+- Allow automatic creation of Kubernetes Integration from template. !16104
+- API: get participants from merge_requests & issues. !16187 (Brent Greeff)
+- Added option to disable commits stats in the commit endpoint. !16309
+- Disable creation of new Kubernetes Integrations unless they're active or created from template. !41054
+- Added badge to tree & blob views to indicate LFS tracked files.
+- Enable ordering of groups and their children by name.
+- Add button to run scheduled pipeline immediately.
+- Allow user to rebase merge requests.
+- Handle GitLab hashed storage repositories using the repo import task.
+- Hide runner token in CI/CD settings page.
+
+### Other (12 changes, 3 of them are from the community)
+
+- Adds the multi file editor as a new beta feature. !15430
+- Use relative URLs when linking to uploaded files. !15751
+- Add docs for why you might be signed out when using the Remember me token. !15756
+- Replace '.team << [user, role]' with 'add_role(user)' in specs. !16069 (@blackst0ne)
+- Add id to modal.vue to support data-toggle="modal". !16189
+- Update scss-lint to 0.56.0. !16278 (Takuya Noguchi)
+- Fix web ide user preferences copy and buttons. !41789
+- Update redis-rack to 2.0.4.
+- Import some code and functionality from gitlab-shell to improve subprocess handling.
+- Update Browse file to Choose file in all occurences.
+- Bump mysql2 gem version from 0.4.5 to 0.4.10. (asaparov)
+- Use a background migration for issues.closed_at.
+
+
+## 10.3.5 (2018-01-18)
+
+- Fix error that prevented the 'deploy_keys' migration from working in MySQL databases.
+
+## 10.3.4 (2018-01-10)
+
+### Security (7 changes, 1 of them is from the community)
+
+- Prevent a SQL injection in the MilestonesFinder.
+- Fix RCE via project import mechanism.
+- Prevent OAuth login POST requests when a provider has been disabled.
+- Filter out sensitive fields from the project services API. (Robert Schilling)
+- Check user authorization for source and target projects when creating a merge request.
+- Fix path traversal in gitlab-ci.yml cache:key.
+- Fix writable shared deploy keys.
+
+
## 10.3.3 (2018-01-02)
### Fixed (3 changes)
@@ -180,6 +361,25 @@ entry.
- Clean up schema of the "merge_requests" table.
+## 10.2.7 (2018-01-18)
+
+- No changes.
+
+## 10.2.6 (2018-01-11)
+
+### Security (9 changes, 1 of them is from the community)
+
+- Fix writable shared deploy keys.
+- Filter out sensitive fields from the project services API. (Robert Schilling)
+- Fix RCE via project import mechanism.
+- Fixed IPython notebook output not being sanitized.
+- Prevent OAuth login POST requests when a provider has been disabled.
+- Prevent a SQL injection in the MilestonesFinder.
+- Check user authorization for source and target projects when creating a merge request.
+- Fix path traversal in gitlab-ci.yml cache:key.
+- Fix XSS vulnerability in pipeline job trace.
+
+
## 10.2.5 (2017-12-15)
### Fixed (8 changes)
@@ -446,6 +646,24 @@ entry.
- Add Gitaly metrics to the performance bar.
+## 10.1.7 (2018-01-18)
+
+- No changes.
+
+## 10.1.6 (2018-01-11)
+
+### Security (8 changes, 1 of them is from the community)
+
+- Fix writable shared deploy keys.
+- Filter out sensitive fields from the project services API. (Robert Schilling)
+- Fix RCE via project import mechanism.
+- Prevent OAuth login POST requests when a provider has been disabled.
+- Prevent a SQL injection in the MilestonesFinder.
+- Check user authorization for source and target projects when creating a merge request.
+- Fix path traversal in gitlab-ci.yml cache:key.
+- Fix XSS vulnerability in pipeline job trace.
+
+
## 10.1.5 (2017-12-07)
### Security (5 changes)
@@ -950,6 +1168,11 @@ entry.
- Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi)
- [BUGIFX] Improves subgroup creation permissions. !13418
+## 9.5.10 (2017-11-08)
+
+- [SECURITY] Add SSRF protections for hostnames that will never resolve but will still connect to localhost
+- [SECURITY] Include X-Content-Type-Options (XCTO) header into API responses
+
## 9.5.9 (2017-10-16)
- [SECURITY] Move project repositories between namespaces when renaming users.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 057e2d6e0dc..b366ae6f069 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -171,7 +171,7 @@ Assigning a team label makes sure issues get the attention of the appropriate
people.
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge,
-~Geo, ~Gitaly, ~Platform, ~Prometheus, ~Release, and ~"UX".
+~Geo, ~Gitaly, ~Platform, ~Monitoring, ~Release, and ~"UX".
The descriptions on the [labels page][labels-page] explain what falls under the
responsibility of each team.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 328185caaeb..534b316aef6 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.67.0
+0.70.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index c68d476cc8e..9b9a244206f 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.11.0
+6.0.2
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 1545d966571..d5c0c991428 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-3.5.0
+3.5.1
diff --git a/Gemfile b/Gemfile
index 5c455ab15e3..6f82890d0d1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -115,7 +115,7 @@ gem 'google-api-client', '~> 0.13.6'
gem 'unf', '~> 0.1.4'
# Seed data
-gem 'seed-fu', '2.3.6' # Upgrade to > 2.3.7 once https://github.com/mbleigh/seed-fu/issues/123 is solved
+gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
@@ -233,6 +233,9 @@ gem 'charlock_holmes', '~> 0.7.5'
# Faster JSON
gem 'oj', '~> 2.17.4'
+# Faster blank
+gem 'fast_blank'
+
# Parse time & duration
gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6'
@@ -338,10 +341,10 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0'
- gem 'gitlab-styles', '~> 2.2.0', require: false
+ gem 'gitlab-styles', '~> 2.3', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines
- gem 'rubocop', '~> 0.52.0'
- gem 'rubocop-rspec', '~> 1.20.1'
+ gem 'rubocop', '~> 0.52.1'
+ gem 'rubocop-rspec', '~> 1.22.1'
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false
@@ -403,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.69.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.73.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index cb6b0ebb3bc..284e1e7654d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -207,6 +207,7 @@ GEM
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
+ fast_blank (1.0.0)
fast_gettext (1.4.0)
ffaker (2.4.0)
ffi (1.9.18)
@@ -284,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.69.0)
+ gitaly-proto (0.73.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -303,7 +304,7 @@ GEM
mime-types (>= 1.16)
posix-spawn (~> 0.3)
gitlab-markup (1.6.3)
- gitlab-styles (2.2.0)
+ gitlab-styles (2.3.0)
rubocop (~> 0.51)
rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19)
@@ -340,6 +341,8 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.4.1.1)
+ googleapis-common-protos-types (1.0.1)
+ google-protobuf (~> 3.0)
googleauth (0.5.3)
faraday (~> 0.12)
jwt (~> 1.4)
@@ -366,9 +369,10 @@ GEM
rake
grape_logging (1.7.0)
grape
- grpc (1.4.5)
+ grpc (1.8.3)
google-protobuf (~> 3.1)
- googleauth (~> 0.5.1)
+ googleapis-common-protos-types (~> 1.0.0)
+ googleauth (>= 0.5.1, < 0.7)
haml (4.0.7)
tilt
haml_lint (0.26.0)
@@ -579,7 +583,7 @@ GEM
rubypants (~> 0.2)
orm_adapter (0.5.0)
os (0.9.6)
- parallel (1.12.0)
+ parallel (1.12.1)
parser (2.4.0.2)
ast (~> 2.3)
parslet (1.5.0)
@@ -782,7 +786,7 @@ GEM
pg
rails
sqlite3
- rubocop (0.52.0)
+ rubocop (0.52.1)
parallel (~> 1.10)
parser (>= 2.4.0.2, < 3.0)
powerpack (~> 0.1)
@@ -791,8 +795,8 @@ GEM
unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-gitlab-security (0.1.1)
rubocop (>= 0.51)
- rubocop-rspec (1.20.1)
- rubocop (>= 0.51.0)
+ rubocop-rspec (1.22.1)
+ rubocop (>= 0.52.1)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-prof (0.16.2)
@@ -828,7 +832,7 @@ GEM
rake (>= 0.9, < 13)
sass (~> 3.5.3)
securecompare (1.0.0)
- seed-fu (2.3.6)
+ seed-fu (2.3.7)
activerecord (>= 3.1)
activesupport (>= 3.1)
select2-rails (3.5.9.3)
@@ -1031,6 +1035,7 @@ DEPENDENCIES
email_spec (~> 1.6.0)
factory_bot_rails (~> 4.8.2)
faraday (~> 0.12)
+ fast_blank
ffaker (~> 2.4)
flay (~> 2.8.0)
flipper (~> 0.11.0)
@@ -1051,11 +1056,11 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.69.0)
+ gitaly-proto (~> 0.73.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
- gitlab-styles (~> 2.2.0)
+ gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
@@ -1157,8 +1162,8 @@ DEPENDENCIES
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5)
- rubocop (~> 0.52.0)
- rubocop-rspec (~> 1.20.1)
+ rubocop (~> 0.52.1)
+ rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8)
@@ -1167,7 +1172,7 @@ DEPENDENCIES
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0)
- seed-fu (= 2.3.6)
+ seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3)
diff --git a/VERSION b/VERSION
index 80959b81ba4..7f6774b38ba 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-10.4.0-pre
+10.5.0-pre
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 21d8c790e90..38c67b5f04e 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -218,6 +218,7 @@ const Api = {
(jqXHR, textStatus, errorThrown) => {
const error = new Error(`${options.url}: ${errorThrown}`);
error.textStatus = textStatus;
+ if (jqXHR && jqXHR.responseJSON) error.responseJSON = jqXHR.responseJSON;
reject(error);
},
);
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index f7ae6f1cd12..83cac896f86 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -4,6 +4,8 @@ import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
+Dropzone.autoDiscover = false;
+
function toggleLoading($el, $icon, loading) {
if (loading) {
$el.disable();
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index adb7360327c..a8dafd31f12 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -1,5 +1,5 @@
/* eslint-disable comma-dangle, space-before-function-paren, one-var */
-/* global Sortable */
+import Sortable from 'vendor/Sortable';
import Vue from 'vue';
import AccessorUtilities from '../../lib/utils/accessor';
import boardList from './board_list';
diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js
index d8cf532fe78..591f1dc8313 100644
--- a/app/assets/javascripts/boards/components/board_list.js
+++ b/app/assets/javascripts/boards/components/board_list.js
@@ -1,4 +1,4 @@
-/* global Sortable */
+import Sortable from 'vendor/Sortable';
import boardNewIssue from './board_new_issue';
import boardCard from './board_card.vue';
import eventHub from '../eventhub';
diff --git a/app/assets/javascripts/boards/utils/query_data.js b/app/assets/javascripts/boards/utils/query_data.js
index 2cd3c146f11..65315979df7 100644
--- a/app/assets/javascripts/boards/utils/query_data.js
+++ b/app/assets/javascripts/boards/utils/query_data.js
@@ -5,7 +5,7 @@ export default (path, extraData) => path.split('&').reduce((dataParam, filterPar
const paramSplit = filterParam.split('=');
const paramKeyNormalized = paramSplit[0].replace('[]', '');
const isArray = paramSplit[0].indexOf('[]');
- const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' ');
+ const value = decodeURIComponent(paramSplit[1].replace(/\+/g, ' '));
if (isArray !== -1) {
if (!data[paramKeyNormalized]) {
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 25cef44c1b8..ff2e0768a87 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -46,14 +46,15 @@
));
const extraCostParagraph = sprintf(
- _.escape(s__(`ClusterIntegration|%{boldNotice} This will add some
-extra resources like a load balancer,
-which incur additional costs. See %{pricingLink}`)),
- {
+ _.escape(s__(
+ `ClusterIntegration|%{boldNotice} This will add some extra resources
+ like a load balancer, which may incur additional costs depending on
+ the hosting provider Kubernetes is installed on. If you are using GKE,
+ you can %{pricingLink}.`,
+ )), {
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
- ${_.escape(s__('ClusterIntegration|GKE pricing'))}
- </a>`,
+ ${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`,
},
false,
);
@@ -80,8 +81,7 @@ which incur additional costs. See %{pricingLink}`)),
{
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer">
- ${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
- </a>`,
+ ${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
},
false,
);
diff --git a/app/assets/javascripts/commit_merge_requests.js b/app/assets/javascripts/commit_merge_requests.js
new file mode 100644
index 00000000000..f76c9b7e690
--- /dev/null
+++ b/app/assets/javascripts/commit_merge_requests.js
@@ -0,0 +1,73 @@
+/* global Flash */
+
+import axios from './lib/utils/axios_utils';
+import { n__, s__ } from './locale';
+
+export function getHeaderText(childElementCount, mergeRequestCount) {
+ if (childElementCount === 0) {
+ return `${mergeRequestCount} ${n__('merge request', 'merge requests', mergeRequestCount)}`;
+ }
+ return ',';
+}
+
+export function createHeader(childElementCount, mergeRequestCount) {
+ const headerText = getHeaderText(childElementCount, mergeRequestCount);
+
+ return $('<span />', {
+ class: 'append-right-5',
+ text: headerText,
+ });
+}
+
+export function createLink(mergeRequest) {
+ return $('<a />', {
+ class: 'append-right-5',
+ href: mergeRequest.path,
+ text: `!${mergeRequest.iid}`,
+ });
+}
+
+export function createTitle(mergeRequest) {
+ return $('<span />', {
+ text: mergeRequest.title,
+ });
+}
+
+export function createItem(mergeRequest) {
+ const $item = $('<span />');
+ const $link = createLink(mergeRequest);
+ const $title = createTitle(mergeRequest);
+ $item.append($link);
+ $item.append($title);
+
+ return $item;
+}
+
+export function createContent(mergeRequests) {
+ const $content = $('<span />');
+
+ if (mergeRequests.length === 0) {
+ $content.text(s__('Commits|No related merge requests found'));
+ } else {
+ mergeRequests.forEach((mergeRequest) => {
+ const $header = createHeader($content.children().length, mergeRequests.length);
+ const $item = createItem(mergeRequest);
+ $content.append($header);
+ $content.append($item);
+ });
+ }
+
+ return $content;
+}
+
+export function fetchCommitMergeRequests() {
+ const $container = $('.merge-requests');
+
+ axios.get($container.data('projectCommitPath'))
+ .then((response) => {
+ const $content = createContent(response.data);
+
+ $container.html($content);
+ })
+ .catch(() => Flash(s__('Commits|An error occurred while fetching merge requests data.')));
+}
diff --git a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js b/app/assets/javascripts/create_item_dropdown.js
index a0224213aa0..488db023ee7 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js
+++ b/app/assets/javascripts/create_item_dropdown.js
@@ -1,6 +1,6 @@
import _ from 'underscore';
-export default class ProtectedTagDropdown {
+export default class CreateItemDropdown {
/**
* @param {Object} options containing
* `$dropdown` target element
@@ -8,11 +8,14 @@ export default class ProtectedTagDropdown {
* $dropdown must be an element created using `dropdown_tag()` rails helper
*/
constructor(options) {
- this.onSelect = options.onSelect;
+ this.defaultToggleLabel = options.defaultToggleLabel;
+ this.fieldName = options.fieldName;
+ this.onSelect = options.onSelect || (() => {});
+ this.getDataOption = options.getData;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
- this.$protectedTag = this.$dropdownContainer.find('.js-create-new-protected-tag');
+ this.$createButton = this.$dropdownContainer.find('.js-dropdown-create-new-item');
this.buildDropdown();
this.bindEvents();
@@ -23,7 +26,7 @@ export default class ProtectedTagDropdown {
buildDropdown() {
this.$dropdown.glDropdown({
- data: this.getProtectedTags.bind(this),
+ data: this.getData.bind(this),
filterable: true,
remote: false,
search: {
@@ -31,14 +34,14 @@ export default class ProtectedTagDropdown {
},
selectable: true,
toggleLabel(selected) {
- return (selected && 'id' in selected) ? selected.title : 'Protected Tag';
+ return (selected && 'id' in selected) ? selected.title : this.defaultToggleLabel;
},
- fieldName: 'protected_tag[name]',
- text(protectedTag) {
- return _.escape(protectedTag.title);
+ fieldName: this.fieldName,
+ text(item) {
+ return _.escape(item.title);
},
- id(protectedTag) {
- return _.escape(protectedTag.id);
+ id(item) {
+ return _.escape(item.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
@@ -49,37 +52,47 @@ export default class ProtectedTagDropdown {
}
bindEvents() {
- this.$protectedTag.on('click', this.onClickCreateWildcard.bind(this));
+ this.$createButton.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard(e) {
+ e.preventDefault();
+
+ // Refresh the dropdown's data, which ends up calling `getData`
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex();
- e.preventDefault();
}
- getProtectedTags(term, callback) {
- if (this.selectedTag) {
- callback(gon.open_tags.concat(this.selectedTag));
- } else {
- callback(gon.open_tags);
- }
+ getData(term, callback) {
+ this.getDataOption(term, (data = []) => {
+ // Ensure the selected item isn't already in the data to avoid duplicates
+ const alreadyHasSelectedItem = this.selectedItem && data.some(item =>
+ item.id === this.selectedItem.id,
+ );
+
+ let uniqueData = data;
+ if (!alreadyHasSelectedItem) {
+ uniqueData = data.concat(this.selectedItem || []);
+ }
+
+ callback(uniqueData);
+ });
}
- toggleCreateNewButton(tagName) {
- if (tagName) {
- this.selectedTag = {
- title: tagName,
- id: tagName,
- text: tagName,
+ toggleCreateNewButton(item) {
+ if (item) {
+ this.selectedItem = {
+ title: item,
+ id: item,
+ text: item,
};
this.$dropdownContainer
- .find('.js-create-new-protected-tag code')
- .text(tagName);
+ .find('.js-dropdown-create-new-item code')
+ .text(item);
}
- this.toggleFooter(!tagName);
+ this.toggleFooter(!item);
}
toggleFooter(toggleState) {
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js
index eedbd3feeb5..bc23a72762f 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/create_merge_request_dropdown.js
@@ -1,4 +1,5 @@
/* eslint-disable no-new */
+import _ from 'underscore';
import Flash from './flash';
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index a9e819b8a3c..843564ce016 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -1,11 +1,15 @@
<script>
import actionBtn from './action_btn.vue';
import { getTimeago } from '../../lib/utils/datetime_utility';
+ import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
actionBtn,
},
+ directives: {
+ tooltip,
+ },
props: {
deployKey: {
type: Object,
@@ -32,6 +36,9 @@
isEnabled(id) {
return this.store.findEnabledKey(id) !== undefined;
},
+ tooltipTitle(project) {
+ return project.can_push ? 'Write access allowed' : 'Read access only';
+ },
},
};
</script>
@@ -52,21 +59,23 @@
<div class="description">
{{ deployKey.fingerprint }}
</div>
- <div
- v-if="deployKey.can_push"
- class="write-access-allowed"
- >
- Write access allowed
- </div>
</div>
<div class="deploy-key-content prepend-left-default deploy-key-projects">
<a
- v-for="(project, i) in deployKey.projects"
- class="label deploy-project-label"
- :href="project.full_path"
+ v-for="(deployKeysProject, i) in deployKey.deploy_keys_projects"
:key="i"
+ class="label deploy-project-label"
+ :href="deployKeysProject.project.full_path"
+ :title="tooltipTitle(deployKeysProject)"
+ v-tooltip
>
- {{ project.full_name }}
+ {{ deployKeysProject.project.full_name }}
+ <i
+ v-if="!deployKeysProject.can_push"
+ aria-hidden="true"
+ class="fa fa-lock"
+ >
+ </i>
</a>
</div>
<div class="deploy-key-content">
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index dbbcf80485e..bd0c2e8fed5 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,79 +1,24 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
-import { s__ } from './locale';
-import projectSelect from './project_select';
-import IssuableIndex from './issuable_index';
import Milestone from './milestone';
-import IssuableForm from './issuable_form';
-import LabelsSelect from './labels_select';
-import MilestoneSelect from './milestone_select';
-import NewBranchForm from './new_branch_form';
-import NotificationsForm from './notifications_form';
import notificationsDropdown from './notifications_dropdown';
-import groupAvatar from './group_avatar';
-import GroupLabelSubscription from './group_label_subscription';
import LineHighlighter from './line_highlighter';
-import NewCommitForm from './new_commit_form';
-import Project from './project';
-import projectAvatar from './project_avatar';
import MergeRequest from './merge_request';
-import Compare from './compare';
-import initCompareAutocomplete from './compare_autocomplete';
-import ProjectFindFile from './project_find_file';
-import ProjectNew from './project_new';
-import projectImport from './project_import';
-import Labels from './labels';
-import LabelManager from './label_manager';
import Sidebar from './right_sidebar';
-import IssuableTemplateSelectors from './templates/issuable_template_selectors';
import Flash from './flash';
-import CommitsList from './commits';
-import Issue from './issue';
-import BindInOut from './behaviors/bind_in_out';
-import SecretValues from './behaviors/secret_values';
-import DeleteModal from './branches/branches_delete_modal';
-import Group from './group';
-import ProjectsList from './projects_list';
-import setupProjectEdit from './project_edit';
-import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
-import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
-import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
-import ShortcutsWiki from './shortcuts_wiki';
import BlobViewer from './blob/viewer/index';
-import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
-import UsersSelect from './users_select';
-import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete';
-import ShortcutsBlob from './shortcuts_blob';
import Star from './star';
-import TreeView from './tree';
-import Wikis from './wikis';
import ZenMode from './zen_mode';
-import initSettingsPanels from './settings_panels';
import PerformanceBar from './performance_bar';
import initNotes from './init_notes';
-import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar';
-import initProjectVisibilitySelector from './project_visibility';
-import GpgBadges from './gpg_badges';
-import initChangesDropdown from './init_changes_dropdown';
-import NewGroupChild from './groups/new_group_child';
-import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
-import AjaxLoadingSpinner from './ajax_loading_spinner';
+import { convertPermissionToBoolean } from './lib/utils/common_utils';
import GlFieldErrors from './gl_field_errors';
-import GLForm from './gl_form';
import Shortcuts from './shortcuts';
-import ShortcutsNavigation from './shortcuts_navigation';
-import ShortcutsFindFile from './shortcuts_find_file';
import ShortcutsIssuable from './shortcuts_issuable';
-import U2FAuthenticate from './u2f/authenticate';
-import Members from './members';
-import memberExpirationDate from './member_expiration_date';
-import DueDateSelectors from './due_date_select';
import Diff from './diff';
-import ProjectLabelSubscription from './project_label_subscription';
import SearchAutocomplete from './search_autocomplete';
-import Activities from './activities';
(function() {
var Dispatcher;
@@ -86,7 +31,7 @@ import Activities from './activities';
}
Dispatcher.prototype.initPageScripts = function() {
- var path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
+ var path, shortcut_handler;
const page = $('body').attr('data-page');
if (!page) {
return false;
@@ -111,35 +56,6 @@ import Activities from './activities';
});
});
- function initBlob() {
- new LineHighlighter();
-
- new BlobLinePermalinkUpdater(
- document.querySelector('#blob-content-holder'),
- '.diff-line-num[data-line-number]',
- document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
- );
-
- shortcut_handler = new ShortcutsNavigation();
- fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
- fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
- new ShortcutsBlob({
- skipResetBindings: true,
- fileBlobPermalinkUrl,
- });
-
- new BlobForkSuggestion({
- openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'),
- forkButtons: document.querySelectorAll('.js-fork-suggestion-button'),
- cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
- suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
- actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
- })
- .init();
- }
-
- const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
-
switch (page) {
case 'sessions:new':
import('./pages/sessions/new')
@@ -148,26 +64,28 @@ import Activities from './activities';
break;
case 'projects:boards:show':
case 'projects:boards:index':
- shortcut_handler = new ShortcutsNavigation();
- new UsersSelect();
+ import('./pages/projects/boards/index')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:merge_requests:index':
+ import('./pages/projects/merge_requests/index')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
+ break;
case 'projects:issues:index':
- if (filteredSearchEnabled) {
- const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
- filteredSearchManager.setup();
- }
- const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_';
- new IssuableIndex(pagePrefix);
-
- shortcut_handler = new ShortcutsNavigation();
- new UsersSelect();
+ import('./pages/projects/issues/index')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:issues:show':
- new Issue();
- shortcut_handler = new ShortcutsIssuable();
- new ZenMode();
- initIssuableSidebar();
+ import('./pages/projects/issues/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'dashboard:milestones:index':
import('./pages/dashboard/milestones/index')
@@ -190,20 +108,28 @@ import Activities from './activities';
.catch(fail);
break;
case 'dashboard:merge_requests':
- projectSelect();
- initLegacyFilters();
+ import('./pages/dashboard/merge_requests')
+ .then(callDefault)
+ .catch(fail);
break;
case 'groups:issues':
+ import('./pages/groups/issues')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'groups:merge_requests':
- if (filteredSearchEnabled) {
- const filteredSearchManager = new gl.FilteredSearchManager(page === 'groups:issues' ? 'issues' : 'merge_requests');
- filteredSearchManager.setup();
- }
- projectSelect();
+ import('./pages/groups/merge_requests')
+ .then(callDefault)
+ .catch(fail);
break;
case 'dashboard:todos:index':
import('./pages/dashboard/todos/index').then(callDefault).catch(fail);
break;
+ case 'admin:jobs:index':
+ import('./pages/admin/jobs/index')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'dashboard:projects:index':
case 'dashboard:projects:starred':
import('./pages/dashboard/projects')
@@ -223,92 +149,123 @@ import Activities from './activities';
.catch(fail);
break;
case 'projects:milestones:new':
+ case 'projects:milestones:create':
+ import('./pages/projects/milestones/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:milestones:edit':
case 'projects:milestones:update':
- new ZenMode();
- new DueDateSelectors();
- new GLForm($('.milestone-form'), true);
+ import('./pages/projects/milestones/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'groups:milestones:new':
+ case 'groups:milestones:create':
+ import('./pages/groups/milestones/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'groups:milestones:edit':
case 'groups:milestones:update':
- new ZenMode();
- new DueDateSelectors();
- new GLForm($('.milestone-form'), false);
+ import('./pages/groups/milestones/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:compare:show':
- new Diff();
- const paddingTop = 16;
- initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
+ import('./pages/projects/compare/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:branches:new':
+ import('./pages/projects/branches/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:branches:create':
- new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
+ import('./pages/projects/branches/new')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:branches:index':
- AjaxLoadingSpinner.init();
- new DeleteModal();
+ import('./pages/projects/branches/index')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:issues:new':
+ import('./pages/projects/issues/new')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
+ break;
case 'projects:issues:edit':
- shortcut_handler = new ShortcutsNavigation();
- new GLForm($('.issue-form'), true);
- new IssuableForm($('.issue-form'));
- new LabelsSelect();
- new MilestoneSelect();
- new IssuableTemplateSelectors();
+ import('./pages/projects/issues/edit')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:merge_requests:creations:new':
- const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
- if (mrNewCompareNode) {
- new Compare({
- targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl,
- sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl,
- targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl,
- });
- } else {
- const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit');
- new MergeRequest({
- action: mrNewSubmitNode.dataset.mrSubmitAction,
- });
- }
+ import('./pages/projects/merge_requests/creations/new')
+ .then(callDefault)
+ .catch(fail);
case 'projects:merge_requests:creations:diffs':
+ import('./pages/projects/merge_requests/creations/diffs')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
+ break;
case 'projects:merge_requests:edit':
- new Diff();
- shortcut_handler = new ShortcutsNavigation();
- new GLForm($('.merge-request-form'), true);
- new IssuableForm($('.merge-request-form'));
- new LabelsSelect();
- new MilestoneSelect();
- new IssuableTemplateSelectors();
- new AutoWidthDropdownSelect($('.js-target-branch-select')).init();
+ import('./pages/projects/merge_requests/edit')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:tags:new':
- new ZenMode();
- new GLForm($('.tag-form'), true);
- new RefSelectDropdown($('.js-branch-select'));
+ import('./pages/projects/tags/new')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:snippets:show':
- initNotes();
- new ZenMode();
+ import('./pages/projects/snippets/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:snippets:new':
- case 'projects:snippets:edit':
case 'projects:snippets:create':
+ import('./pages/projects/snippets/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
+ case 'projects:snippets:edit':
case 'projects:snippets:update':
- new GLForm($('.snippet-form'), true);
- new ZenMode();
+ import('./pages/projects/snippets/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'snippets:new':
+ import('./pages/snippets/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'snippets:edit':
+ import('./pages/snippets/edit')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'snippets:create':
+ import('./pages/snippets/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'snippets:update':
- new GLForm($('.snippet-form'), false);
- new ZenMode();
+ import('./pages/snippets/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:releases:edit':
- new ZenMode();
- new GLForm($('.release-form'), true);
+ import('./pages/projects/releases/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:merge_requests:show':
import('./pages/projects/merge_requests/show')
@@ -322,22 +279,15 @@ import Activities from './activities';
.catch(fail);
break;
case 'projects:commit:show':
- new Diff();
- new ZenMode();
- shortcut_handler = new ShortcutsNavigation();
- new MiniPipelineGraph({
- container: '.js-commit-pipeline-graph',
- }).bindEvents();
- initNotes();
- const stickyBarPaddingTop = 16;
- initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
- $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ import('./pages/projects/commit/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:commit:pipelines':
- new MiniPipelineGraph({
- container: '.js-commit-pipeline-graph',
- }).bindEvents();
- $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ import('./pages/projects/commit/pipelines')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:activity':
import('./pages/projects/activity')
@@ -346,32 +296,26 @@ import Activities from './activities';
shortcut_handler = true;
break;
case 'projects:commits:show':
- CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
- shortcut_handler = new ShortcutsNavigation();
- GpgBadges.fetch();
+ import('./pages/projects/commits/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:show':
- shortcut_handler = new ShortcutsNavigation();
- new NotificationsForm();
- new UserCallout({
- setCalloutPerProject: true,
- className: 'js-autodevops-banner',
- });
-
- if ($('#tree-slider').length) new TreeView();
- if ($('.blob-viewer').length) new BlobViewer();
- if ($('.project-show-activity').length) new Activities();
- $('#tree-slider').waitForImages(function() {
- ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
- });
+ import('./pages/projects/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:edit':
- setupProjectEdit();
- // Initialize expandable settings panels
- initSettingsPanels();
+ import('./pages/projects/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:imports:show':
- projectImport();
+ import('./pages/projects/imports/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:pipelines:new':
case 'projects:pipelines:create':
@@ -387,34 +331,36 @@ import Activities from './activities';
.catch(fail);
break;
case 'groups:activity':
- new Activities();
+ import('./pages/groups/activity')
+ .then(callDefault)
+ .catch(fail);
break;
case 'groups:show':
- const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
- shortcut_handler = new ShortcutsNavigation();
- new NotificationsForm();
- notificationsDropdown();
- new ProjectsList();
-
- if (newGroupChildWrapper) {
- new NewGroupChild(newGroupChildWrapper);
- }
+ import('./pages/groups/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'groups:group_members:index':
- memberExpirationDate();
- new Members();
- new UsersSelect();
+ import('./pages/groups/group_members/index')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:project_members:index':
import('./pages/projects/project_members/')
.then(callDefault)
.catch(fail);
break;
- case 'groups:new':
case 'groups:create':
- BindInOut.initAll();
- new Group();
- groupAvatar();
+ case 'groups:new':
+ import('./pages/groups/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
+ case 'groups:edit':
+ import('./pages/groups/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'admin:groups:create':
case 'admin:groups:new':
@@ -427,55 +373,59 @@ import Activities from './activities';
.then(callDefault)
.catch(fail);
break;
- case 'groups:edit':
- groupAvatar();
- break;
case 'projects:tree:show':
- shortcut_handler = new ShortcutsNavigation();
- new TreeView();
- new BlobViewer();
- new NewCommitForm($('.js-create-dir-form'));
- $('#tree-slider').waitForImages(function() {
- ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
- });
+ import('./pages/projects/tree/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:find_file:show':
- const findElement = document.querySelector('.js-file-finder');
- const projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
- url: findElement.dataset.fileFindUrl,
- treeUrl: findElement.dataset.findTreeUrl,
- blobUrlTemplate: findElement.dataset.blobUrlTemplate,
- });
- new ShortcutsFindFile(projectFindFile);
+ import('./pages/projects/find_file/show')
+ .then(callDefault)
+ .catch(fail);
shortcut_handler = true;
break;
case 'projects:blob:show':
- new BlobViewer();
- initBlob();
+ import('./pages/projects/blob/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:blame:show':
- initBlob();
+ import('./pages/projects/blame/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'groups:labels:new':
+ import('./pages/groups/labels/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'groups:labels:edit':
+ import('./pages/groups/labels/edit')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:labels:new':
+ import('./pages/projects/labels/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:labels:edit':
- new Labels();
+ import('./pages/projects/labels/edit')
+ .then(callDefault)
+ .catch(fail);
break;
case 'groups:labels:index':
+ import('./pages/groups/labels/index')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:labels:index':
- if ($('.prioritized-labels').length) {
- new LabelManager();
- }
- $('.label-subscription').each((i, el) => {
- const $el = $(el);
-
- if ($el.find('.dropdown-group-label').length) {
- new GroupLabelSubscription($el);
- } else {
- new ProjectLabelSubscription($el);
- }
- });
+ import('./pages/projects/labels/index')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:network:show':
// Ensure we don't create a particular shortcut handler here. This is
@@ -483,7 +433,7 @@ import Activities from './activities';
shortcut_handler = true;
break;
case 'projects:forks:new':
- import(/* webpackChunkName: 'project_fork' */ './project_fork')
+ import('./pages/projects/forks/new')
.then(callDefault)
.catch(fail);
break;
@@ -510,24 +460,19 @@ import Activities from './activities';
.catch(fail);
break;
case 'projects:settings:repository:show':
- // Initialize expandable settings panels
- initSettingsPanels();
+ import('./pages/projects/settings/repository/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:settings:ci_cd:show':
- // Initialize expandable settings panels
- initSettingsPanels();
-
- const runnerToken = document.querySelector('.js-secret-runner-token');
- if (runnerToken) {
- const runnerTokenSecretValue = new SecretValues(runnerToken);
- runnerTokenSecretValue.init();
- }
+ import('./pages/projects/settings/ci_cd/show')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'groups:settings:ci_cd:show':
- const secretVariableTable = document.querySelector('.js-secret-variable-table');
- if (secretVariableTable) {
- const secretVariableTableValues = new SecretValues(secretVariableTable);
- secretVariableTableValues.init();
- }
+ import('./pages/groups/settings/ci_cd/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'ci:lints:create':
case 'ci:lints:show':
@@ -540,7 +485,9 @@ import Activities from './activities';
import('./pages/admin/conversational_development_index/show').then(m => m.default()).catch(fail);
break;
case 'snippets:show':
- import('./pages/snippets/show').then(m => m.default()).catch(fail);
+ import('./pages/snippets/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'import:fogbugz:new_user_map':
import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail);
@@ -556,36 +503,29 @@ import Activities from './activities';
.catch(fail);
break;
case 'projects:clusters:show':
- import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle')
- .then(cluster => new cluster.default()) // eslint-disable-line new-cap
- .catch((err) => {
- Flash(s__('ClusterIntegration|Problem setting up the cluster'));
- throw err;
- });
+ case 'projects:clusters:update':
+ case 'projects:clusters:destroy':
+ import('./pages/projects/clusters/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:clusters:index':
- import(/* webpackChunkName: "clusters_index" */ './clusters/clusters_index')
- .then(clusterIndex => clusterIndex.default())
- .catch((err) => {
- Flash(s__('ClusterIntegration|Problem setting up the clusters list'));
- throw err;
- });
+ import('./pages/projects/clusters/index')
+ .then(callDefault)
+ .catch(fail);
break;
}
switch (path[0]) {
case 'sessions':
+ import('./pages/sessions')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'omniauth_callbacks':
- if (!gon.u2f) break;
- const u2fAuthenticate = new U2FAuthenticate(
- $('#js-authenticate-u2f'),
- '#js-login-u2f-form',
- gon.u2f,
- document.querySelector('#js-login-2fa-device'),
- document.querySelector('.js-2fa-form'),
- );
- u2fAuthenticate.start();
- // needed in rspec
- gl.u2fAuthenticate = u2fAuthenticate;
+ import('./pages/omniauth_callbacks')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'admin':
import('./pages/admin')
.then(callDefault)
@@ -635,65 +575,45 @@ import Activities from './activities';
break;
}
break;
- case 'dashboard':
- case 'root':
- new UserCallout();
- break;
case 'profiles':
import('./pages/profiles/index/')
.then(callDefault)
.catch(fail);
break;
case 'projects':
- new Project();
- projectAvatar();
+ import('./pages/projects')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
switch (path[1]) {
case 'compare':
- initCompareAutocomplete();
- break;
- case 'edit':
- shortcut_handler = new ShortcutsNavigation();
- new ProjectNew();
- import(/* webpackChunkName: 'project_permissions' */ './projects/permissions')
+ import('./pages/projects/compare')
.then(callDefault)
.catch(fail);
break;
+ case 'create':
case 'new':
- new ProjectNew();
- initProjectVisibilitySelector();
+ import('./pages/projects/new')
+ .then(callDefault)
+ .catch(fail);
break;
case 'show':
new Star();
- new ProjectNew();
notificationsDropdown();
break;
case 'wikis':
- new Wikis();
- shortcut_handler = new ShortcutsWiki();
- new ZenMode();
- new GLForm($('.wiki-form'), true);
+ import('./pages/projects/wikis')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'snippets':
- shortcut_handler = new ShortcutsNavigation();
if (path[2] === 'show') {
new ZenMode();
new LineHighlighter();
new BlobViewer();
}
break;
- case 'labels':
- case 'graphs':
- case 'compare':
- case 'pipelines':
- case 'forks':
- case 'milestones':
- case 'project_members':
- case 'deploy_keys':
- case 'builds':
- case 'hooks':
- case 'services':
- case 'protected_branches':
- shortcut_handler = new ShortcutsNavigation();
}
break;
}
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index c84be42649a..550dbdda922 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -3,6 +3,8 @@ import _ from 'underscore';
import './preview_markdown';
import csrf from './lib/utils/csrf';
+Dropzone.autoDiscover = false;
+
export default function dropzoneInput(form) {
const divHover = '<div class="div-dropzone-hover"></div>';
const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index 7219b076721..34d18d55120 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -1,7 +1,7 @@
/**
* Common code between environmets app and folder view
*/
-
+import _ from 'underscore';
import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll';
import {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 46c80dfd45e..ff046aa286a 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container';
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index c05a83176f2..58ed0012f01 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index 6139e81fe6d..2e859d2de3a 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import AjaxCache from '../lib/utils/ajax_cache';
import Flash from '../flash';
import FilteredSearchContainer from './container';
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index 44deab9288e..a0af2875ab5 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => {
flashEl.addEventListener('transitionend', () => {
flashEl.remove();
+ if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
}, {
once: true,
passive: true,
@@ -64,6 +65,7 @@ const createFlash = function createFlash(
parent = document,
actionConfig = null,
fadeTransition = true,
+ addBodyClass = false,
) {
const flashContainer = parent.querySelector('.flash-container');
@@ -86,6 +88,8 @@ const createFlash = function createFlash(
flashContainer.style.display = 'block';
+ if (addBodyClass) document.body.classList.add('flash-shown');
+
return flashContainer;
};
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 5279417a72a..96b1bb78c1d 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -68,12 +68,8 @@ export default {
this.commitChanges({ payload, newMr: this.startNewMR })
.then(() => {
this.submitCommitsLoading = false;
- this.$store.dispatch('getTreeData', {
- projectId: this.currentProjectId,
- branch: this.currentBranchId,
- endpoint: `/tree/${this.currentBranchId}`,
- force: true,
- });
+ this.commitMessage = '';
+ this.startNewMR = false;
})
.catch(() => {
this.submitCommitsLoading = false;
@@ -153,6 +149,7 @@ you started editing. Would you like to create a new branch?`)"
type="submit"
:disabled="commitButtonDisabled"
class="btn btn-default btn-sm append-right-10 prepend-left-10"
+ :class="{ disabled: submitCommitsLoading }"
>
<i
v-if="submitCommitsLoading"
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 83b82ae44c9..f99228012f4 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -70,7 +70,10 @@ export default {
this.editor.createInstance(this.$refs.editor);
})
.then(() => this.setupEditor())
- .catch(() => flash('Error setting up monaco. Please try again.'));
+ .catch((err) => {
+ flash('Error setting up monaco. Please try again.', 'alert', document, null, false, true);
+ throw err;
+ });
},
setupEditor() {
if (!this.activeFile) return;
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index f7f4db89bdf..110918872fb 100644
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -35,9 +35,12 @@
return this.file.type === 'tree';
},
levelIndentation() {
- return {
- marginLeft: `${this.file.level * 16}px`,
- };
+ if (this.file.level > 0) {
+ return {
+ marginLeft: `${this.file.level * 16}px`,
+ };
+ }
+ return {};
},
shortId() {
return this.file.id.substr(0, 8);
@@ -111,7 +114,7 @@
/>
<i
class="fa"
- v-if="changedClass"
+ v-if="file.changed || file.tempFile"
:class="changedClass"
aria-hidden="true"
>
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index a9cbf8e370f..a7fb9e0588a 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -84,13 +84,13 @@ router.beforeEach((to, from, next) => {
}
})
.catch((e) => {
- flash('Error while loading the branch files. Please try again.');
+ flash('Error while loading the branch files. Please try again.', 'alert', document, null, false, true);
throw e;
});
}
})
.catch((e) => {
- flash('Error while loading the project data. Please try again.');
+ flash('Error while loading the project data. Please try again.', 'alert', document, null, false, true);
throw e;
});
}
diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js
index 51e202b9348..51255f15658 100644
--- a/app/assets/javascripts/ide/lib/editor.js
+++ b/app/assets/javascripts/ide/lib/editor.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import DecorationsController from './decorations/controller';
import DirtyDiffController from './diff/controller';
import Disposable from './common/disposable';
@@ -54,7 +55,7 @@ export default class Editor {
attachModel(model) {
this.instance.setModel(model.getModel());
- this.dirtyDiffController.attachModel(model);
+ if (this.dirtyDiffController) this.dirtyDiffController.attachModel(model);
this.currentModel = model;
@@ -67,7 +68,7 @@ export default class Editor {
return acc;
}, {}));
- this.dirtyDiffController.reDecorate(model);
+ if (this.dirtyDiffController) this.dirtyDiffController.reDecorate(model);
}
clearEditor() {
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index 335882bb6d7..96a87744df5 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -3,6 +3,7 @@ import { visitUrl } from '../../lib/utils/url_utility';
import flash from '../../flash';
import service from '../services';
import * as types from './mutation_types';
+import { stripHtml } from '../../lib/utils/text_utility';
export const redirectToUrl = (_, url) => visitUrl(url);
@@ -81,7 +82,7 @@ export const checkCommitStatus = ({ state }) =>
return false;
})
- .catch(() => flash('Error checking branch data. Please try again.'));
+ .catch(() => flash('Error checking branch data. Please try again.', 'alert', document, null, false, true));
export const commitChanges = (
{ commit, state, dispatch, getters },
@@ -92,7 +93,7 @@ export const commitChanges = (
.then((data) => {
const { branch } = payload;
if (!data.short_id) {
- flash(data.message);
+ flash(data.message, 'alert', document, null, false, true);
return;
}
@@ -105,19 +106,25 @@ export const commitChanges = (
},
};
+ let commitMsg = `Your changes have been committed. Commit ${data.short_id}`;
+ if (data.stats) {
+ commitMsg += ` with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`;
+ }
+
flash(
- `Your changes have been committed. Commit ${data.short_id} with ${
- data.stats.additions
- } additions, ${data.stats.deletions} deletions.`,
+ commitMsg,
'notice',
- );
+ document,
+ null,
+ false,
+ true);
+ window.dispatchEvent(new Event('resize'));
if (newMr) {
+ dispatch('discardAllChanges');
dispatch(
'redirectToUrl',
- `${
- selectedProject.web_url
- }/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`,
+ `${selectedProject.web_url}/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`,
);
} else {
commit(types.SET_BRANCH_WORKING_REFERENCE, {
@@ -134,12 +141,18 @@ export const commitChanges = (
});
dispatch('discardAllChanges');
- dispatch('closeAllFiles');
window.scrollTo(0, 0);
}
})
- .catch(() => flash('Error committing changes. Please try again.'));
+ .catch((err) => {
+ let errMsg = 'Error committing changes. Please try again.';
+ if (err.responseJSON && err.responseJSON.message) {
+ errMsg += ` (${stripHtml(err.responseJSON.message)})`;
+ }
+ flash(errMsg, 'alert', document, null, false, true);
+ window.dispatchEvent(new Event('resize'));
+ });
export const createTempEntry = (
{ state, dispatch },
diff --git a/app/assets/javascripts/ide/stores/actions/branch.js b/app/assets/javascripts/ide/stores/actions/branch.js
index 32bdf7fec22..589ec28c6a4 100644
--- a/app/assets/javascripts/ide/stores/actions/branch.js
+++ b/app/assets/javascripts/ide/stores/actions/branch.js
@@ -17,7 +17,7 @@ export const getBranchData = (
resolve(data);
})
.catch(() => {
- flash('Error loading branch data. Please try again.');
+ flash('Error loading branch data. Please try again.', 'alert', document, null, false, true);
reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
});
} else {
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 0f27d5bf1c3..670af2fb89e 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -69,7 +69,7 @@ export const getFileData = ({ state, commit, dispatch }, file) => {
})
.catch(() => {
commit(types.TOGGLE_LOADING, file);
- flash('Error loading file data. Please try again.');
+ flash('Error loading file data. Please try again.', 'alert', document, null, false, true);
});
};
@@ -77,22 +77,28 @@ export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFile
.then((raw) => {
commit(types.SET_FILE_RAW_DATA, { file, raw });
})
- .catch(() => flash('Error loading file content. Please try again.'));
+ .catch(() => flash('Error loading file content. Please try again.', 'alert', document, null, false, true));
export const changeFileContent = ({ commit }, { file, content }) => {
commit(types.UPDATE_FILE_CONTENT, { file, content });
};
export const setFileLanguage = ({ state, commit }, { fileLanguage }) => {
- commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage });
+ if (state.selectedFile) {
+ commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage });
+ }
};
export const setFileEOL = ({ state, commit }, { eol }) => {
- commit(types.SET_FILE_EOL, { file: state.selectedFile, eol });
+ if (state.selectedFile) {
+ commit(types.SET_FILE_EOL, { file: state.selectedFile, eol });
+ }
};
export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => {
- commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn });
+ if (state.selectedFile) {
+ commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn });
+ }
};
export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => {
@@ -112,7 +118,7 @@ export const createTempFile = ({ state, commit, dispatch }, { projectId, branchI
url: newUrl,
});
- if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`);
+ if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`, 'alert', document, null, false, true);
commit(types.CREATE_TMP_FILE, {
parent,
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 02d4bd87ab0..faeceb430a2 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -18,7 +18,7 @@ export const getProjectData = (
resolve(data);
})
.catch(() => {
- flash('Error loading project data. Please try again.');
+ flash('Error loading project data. Please try again.', 'alert', document, null, false, true);
reject(new Error(`Project not loaded ${namespace}/${projectId}`));
});
} else {
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
index 25909400a75..302ba45edee 100644
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -52,7 +52,7 @@ export const getTreeData = (
resolve(data);
})
.catch((e) => {
- flash('Error loading tree data. Please try again.');
+ flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
if (tree) commit(types.TOGGLE_LOADING, tree);
reject(e);
});
@@ -151,7 +151,7 @@ export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = s
dispatch('getLastCommitData', tree);
})
- .catch(() => flash('Error fetching log data.'));
+ .catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
};
export const updateDirectoryData = (
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index 5f3655b0092..72db1c180c9 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -64,7 +64,7 @@ export default {
},
[types.DISCARD_FILE_CHANGES](state, file) {
Object.assign(file, {
- content: '',
+ content: file.raw,
changed: false,
});
},
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 29e3ab5d040..d556404faa5 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -1,3 +1,5 @@
+import _ from 'underscore';
+
export const dataStructure = () => ({
id: '',
key: '',
diff --git a/app/assets/javascripts/init_labels.js b/app/assets/javascripts/init_labels.js
new file mode 100644
index 00000000000..5f20055510f
--- /dev/null
+++ b/app/assets/javascripts/init_labels.js
@@ -0,0 +1,18 @@
+import LabelManager from './label_manager';
+import GroupLabelSubscription from './group_label_subscription';
+import ProjectLabelSubscription from './project_label_subscription';
+
+export default () => {
+ if ($('.prioritized-labels').length) {
+ new LabelManager(); // eslint-disable-line no-new
+ }
+ $('.label-subscription').each((i, el) => {
+ const $el = $(el);
+
+ if ($el.find('.dropdown-group-label').length) {
+ new GroupLabelSubscription($el); // eslint-disable-line no-new
+ } else {
+ new ProjectLabelSubscription($el); // eslint-disable-line no-new
+ }
+ });
+};
diff --git a/app/assets/javascripts/issuable/auto_width_dropdown_select.js b/app/assets/javascripts/issuable/auto_width_dropdown_select.js
index 2203a56315e..14a2bfbe4e0 100644
--- a/app/assets/javascripts/issuable/auto_width_dropdown_select.js
+++ b/app/assets/javascripts/issuable/auto_width_dropdown_select.js
@@ -11,6 +11,14 @@ class AutoWidthDropdownSelect {
const dropdownClass = this.dropdownClass;
this.$selectElement.select2({
dropdownCssClass: dropdownClass,
+ ...AutoWidthDropdownSelect.selectOptions(this.dropdownClass),
+ });
+
+ return this;
+ }
+
+ static selectOptions(dropdownClass) {
+ return {
dropdownCss() {
let resultantWidth = 'auto';
const $dropdown = $(`.${dropdownClass}`);
@@ -29,9 +37,7 @@ class AutoWidthDropdownSelect {
maxWidth: offsetParentWidth,
};
},
- });
-
- return this;
+ };
}
}
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 57dcaa0e1ac..fdfad0b6a4f 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -6,6 +6,7 @@ import Autosave from './autosave';
import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode';
+import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
export default class IssuableForm {
@@ -46,6 +47,12 @@ export default class IssuableForm {
});
calendar.setDate(parsePikadayDate($issuableDueDate.val()));
}
+
+ this.$targetBranchSelect = $('.js-target-branch-select', this.form);
+
+ if (this.$targetBranchSelect.length) {
+ this.initTargetBranchDropdown();
+ }
}
initAutosave() {
@@ -104,4 +111,37 @@ export default class IssuableForm {
addWip() {
this.titleField.val(`WIP: ${(this.titleField.val())}`);
}
+
+ initTargetBranchDropdown() {
+ this.$targetBranchSelect.select2({
+ ...AutoWidthDropdownSelect.selectOptions('js-target-branch-select'),
+ ajax: {
+ url: this.$targetBranchSelect.data('endpoint'),
+ dataType: 'JSON',
+ quietMillis: 250,
+ data(search) {
+ return {
+ search,
+ };
+ },
+ results(data) {
+ return {
+ // `data` keys are translated so we can't just access them with a string based key
+ results: data[Object.keys(data)[0]].map(name => ({
+ id: name,
+ text: name,
+ })),
+ };
+ },
+ },
+ initSelection(el, callback) {
+ const val = el.val();
+
+ callback({
+ id: val,
+ text: val,
+ });
+ },
+ });
+ }
}
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index f85d66e9b1d..e87a8ed7fea 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -152,6 +152,13 @@
hasUpdated() {
return !!this.state.updatedAt;
},
+ issueChanged() {
+ const descriptionChanged =
+ this.initialDescriptionText !== this.store.formState.description;
+ const titleChanged =
+ this.initialTitleText !== this.store.formState.title;
+ return descriptionChanged || titleChanged;
+ },
},
created() {
this.service = new Service(this.endpoint);
@@ -176,6 +183,8 @@
}
});
+ window.addEventListener('beforeunload', this.handleBeforeUnloadEvent);
+
eventHub.$on('delete.issuable', this.deleteIssuable);
eventHub.$on('update.issuable', this.updateIssuable);
eventHub.$on('close.form', this.closeForm);
@@ -186,8 +195,17 @@
eventHub.$off('update.issuable', this.updateIssuable);
eventHub.$off('close.form', this.closeForm);
eventHub.$off('open.form', this.openForm);
+ window.removeEventListener('beforeunload', this.handleBeforeUnloadEvent);
},
methods: {
+ handleBeforeUnloadEvent(e) {
+ const event = e;
+ if (this.showForm && this.issueChanged) {
+ event.returnValue = 'Are you sure you want to lose your issue information?';
+ }
+ return undefined;
+ },
+
openForm() {
if (!this.showForm) {
this.showForm = true;
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index 8f32dcc94e2..9b5092c5e3f 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -3,7 +3,6 @@ import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
import { numberToHumanSize } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
-import { timeFor } from './lib/utils/datetime_utility';
export default class Job {
constructor(options) {
@@ -71,7 +70,6 @@ export default class Job {
.off('resize.build')
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
- this.updateArtifactRemoveDate();
this.initAffixTopArea();
this.getBuildTrace();
@@ -261,16 +259,7 @@ export default class Job {
sidebarOnClick() {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
}
- // eslint-disable-next-line class-methods-use-this, consistent-return
- updateArtifactRemoveDate() {
- const $date = $('.js-artifacts-remove');
- if ($date.length) {
- const date = $date.text();
- return $date.text(
- timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3'))),
- );
- }
- }
+
// eslint-disable-next-line class-methods-use-this
populateJobs(stage) {
$('.build-job').hide();
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
index 9e3f659db5f..357bc9aab17 100644
--- a/app/assets/javascripts/jobs/components/header.vue
+++ b/app/assets/javascripts/jobs/components/header.vue
@@ -30,8 +30,12 @@
shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length;
},
+ /**
+ * When job has not started the key will be `false`
+ * When job started the key will be a string with a date.
+ */
jobStarted() {
- return this.job.started;
+ return !this.job.started === false;
},
},
watch: {
@@ -72,6 +76,7 @@
<loading-icon
v-if="isLoading"
size="2"
+ class="prepend-top-default append-bottom-default"
/>
</div>
</template>
diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js
index c929dc98c10..ac2f636df0f 100644
--- a/app/assets/javascripts/label_manager.js
+++ b/app/assets/javascripts/label_manager.js
@@ -1,5 +1,5 @@
/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
-/* global Sortable */
+import Sortable from 'vendor/Sortable';
import Flash from './flash';
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index f7a1c9f1e40..664e793fc8e 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -231,7 +231,7 @@ export default class LabelsSelect {
selectedClass.push('label-item');
$a.attr('data-label-id', label.id);
}
- $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title);
+ $a.addClass(selectedClass.join(' ')).html(`${colorEl} ${_.escape(label.title)}`);
// Return generated html
return $li.html($a).prop('outerHTML');
},
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index cb6e06ea584..62d80c4a649 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -72,4 +72,4 @@ export function capitalizeFirstCharacter(text) {
* @param {*} replace
* @returns {String}
*/
-export const stripeHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace);
+export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace);
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index ce6f91439b4..d8b881a8fac 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,33 +1,17 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
+/* eslint-disable import/first */
/* global ConfirmDangerModal */
import jQuery from 'jquery';
-import _ from 'underscore';
import Cookies from 'js-cookie';
-import Dropzone from 'dropzone';
-import Sortable from 'vendor/Sortable';
import svg4everybody from 'svg4everybody';
-// libraries with import side-effects
-import 'mousetrap';
-import 'mousetrap/plugins/pause/mousetrap-pause';
-
// expose common libraries as globals (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;
-window._ = _;
-window.Dropzone = Dropzone;
-window.Sortable = Sortable;
-
-// templates
-import './templates/issuable_template_selector';
-import './templates/issuable_template_selectors';
-
-import './commit/image_file';
// lib/utils
import { handleLocationHash } from './lib/utils/common_utils';
-import { localTimeAgo, renderTimeago } from './lib/utils/datetime_utility';
+import { localTimeAgo } from './lib/utils/datetime_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// behaviors
@@ -43,7 +27,6 @@ import initTodoToggle from './header';
import initImporterStatus from './importer_status';
import initLayoutNav from './layout_nav';
import LazyLoader from './lazy_loader';
-import './line_highlighter';
import initLogoAnimation from './logo';
import './milestone_select';
import './projects_dropdown';
@@ -55,11 +38,9 @@ import './dispatcher';
// eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
-Dropzone.autoDiscover = false;
-
svg4everybody();
-document.addEventListener('beforeunload', function () {
+document.addEventListener('beforeunload', () => {
// Unbind scroll events
$(document).off('scroll');
// Close any open tooltips
@@ -76,16 +57,15 @@ window.addEventListener('load', function onLoad() {
gl.lazyLoader = new LazyLoader({
scrollContainer: window,
- observerNode: '#content-body'
+ observerNode: '#content-body',
});
-$(function () {
- var $body = $('body');
- var $document = $(document);
- var $window = $(window);
- var $sidebarGutterToggle = $('.js-sidebar-toggle');
- var bootstrapBreakpoint = bp.getBreakpointSize();
- var fitSidebarForSize;
+$(() => {
+ const $body = $('body');
+ const $document = $(document);
+ const $window = $(window);
+ const $sidebarGutterToggle = $('.js-sidebar-toggle');
+ let bootstrapBreakpoint = bp.getBreakpointSize();
initBreadcrumbs();
initLayoutNav();
@@ -97,8 +77,8 @@ $(function () {
Cookies.defaults.path = gon.relative_url_root || '/';
// `hashchange` is not triggered when link target is already in window.location
- $body.on('click', 'a[href^="#"]', function() {
- var href = this.getAttribute('href');
+ $body.on('click', 'a[href^="#"]', function clickHashLinkCallback() {
+ const href = this.getAttribute('href');
if (href.substr(1) === getLocationHash()) {
setTimeout(handleLocationHash, 1);
}
@@ -113,155 +93,162 @@ $(function () {
}
// prevent default action for disabled buttons
- $('.btn').click(function(e) {
+ $('.btn').click(function clickDisabledButtonCallback(e) {
if ($(this).hasClass('disabled')) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
+
+ return true;
});
- $('.js-select-on-focus').on('focusin', function () {
- return $(this).select().one('mouseup', function (e) {
- return e.preventDefault();
- });
// Click a .js-select-on-focus field, select the contents
// Prevent a mouseup event from deselecting the input
+ $('.js-select-on-focus').on('focusin', function selectOnFocusCallback() {
+ $(this).select().one('mouseup', (e) => {
+ e.preventDefault();
+ });
});
- $('.remove-row').bind('ajax:success', function () {
+
+ $('.remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() {
$(this).tooltip('destroy')
.closest('li')
.fadeOut();
});
- $('.js-remove-tr').bind('ajax:before', function () {
- return $(this).hide();
+
+ $('.js-remove-tr').on('ajax:before', function removeTRAjaxBeforeCallback() {
+ $(this).hide();
});
- $('.js-remove-tr').bind('ajax:success', function () {
- return $(this).closest('tr').fadeOut();
+
+ $('.js-remove-tr').on('ajax:success', function removeTRAjaxSuccessCallback() {
+ $(this).closest('tr').fadeOut();
});
+
+ // Initialize select2 selects
$('select.select2').select2({
width: 'resolve',
- // Initialize select2 selects
- dropdownAutoWidth: true
+ dropdownAutoWidth: true,
});
- $('.js-select2').bind('select2-close', function () {
- return setTimeout((function () {
- $('.select2-container-active').removeClass('select2-container-active');
- return $(':focus').blur();
- }), 1);
+
// Close select2 on escape
+ $('.js-select2').on('select2-close', () => {
+ setTimeout(() => {
+ $('.select2-container-active').removeClass('select2-container-active');
+ $(':focus').blur();
+ }, 1);
});
+
// Initialize tooltips
$.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover';
$body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]',
- placement: function (tip, el) {
+ placement(tip, el) {
return $(el).data('placement') || 'bottom';
- }
+ },
});
+
// Initialize popovers
$body.popover({
selector: '[data-toggle="popover"]',
trigger: 'focus',
// set the viewport to the main content, excluding the navigation bar, so
// the navigation can't overlap the popover
- viewport: '.layout-page'
+ viewport: '.layout-page',
});
- $('.trigger-submit').on('change', function () {
- return $(this).parents('form').submit();
+
// Form submitter
+ $('.trigger-submit').on('change', function triggerSubmitCallback() {
+ $(this).parents('form').submit();
});
+
localTimeAgo($('abbr.timeago, .js-timeago'), true);
+
// Disable form buttons while a form is submitting
- $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
- var buttons;
- buttons = $('[type="submit"], .js-disable-on-submit', this);
+ $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function ajaxCompleteCallback(e) {
+ const $buttons = $('[type="submit"], .js-disable-on-submit', this);
switch (e.type) {
case 'ajax:beforeSend':
case 'submit':
- return buttons.disable();
+ return $buttons.disable();
default:
- return buttons.enable();
+ return $buttons.enable();
}
});
- $(document).ajaxError(function (e, xhrObj) {
- var ref = xhrObj.status;
- if (xhrObj.status === 401) {
- return new Flash('You need to be logged in.', 'alert');
+
+ $(document).ajaxError((e, xhrObj) => {
+ const ref = xhrObj.status;
+
+ if (ref === 401) {
+ Flash('You need to be logged in.');
} else if (ref === 404 || ref === 500) {
- return new Flash('Something went wrong on our end.', 'alert');
+ Flash('Something went wrong on our end.');
}
});
- $('.account-box').hover(function () {
- // Show/Hide the profile menu when hovering the account box
- return $(this).toggleClass('hover');
- });
- $document.on('click', '.diff-content .js-show-suppressed-diff', function () {
- var $container;
- $container = $(this).parent();
- $container.next('table').show();
- return $container.remove();
+
// Commit show suppressed diff
+ $document.on('click', '.diff-content .js-show-suppressed-diff', function showDiffCallback() {
+ const $container = $(this).parent();
+ $container.next('table').show();
+ $container.remove();
});
+
$('.navbar-toggle').on('click', () => {
$('.header-content').toggleClass('menu-expanded');
gl.lazyLoader.loadCheck();
});
+
// Show/hide comments on diff
- $body.on('click', '.js-toggle-diff-comments', function (e) {
- var $this = $(this);
- var notesHolders = $this.closest('.diff-file').find('.notes_holder');
+ $body.on('click', '.js-toggle-diff-comments', function toggleDiffCommentsCallback(e) {
+ const $this = $(this);
+ const notesHolders = $this.closest('.diff-file').find('.notes_holder');
+
+ e.preventDefault();
+
$this.toggleClass('active');
+
if ($this.hasClass('active')) {
notesHolders.show().find('.hide, .content').show();
} else {
notesHolders.hide().find('.content').hide();
}
+
$(document).trigger('toggle.comments');
- return e.preventDefault();
});
- $document.off('click', '.js-confirm-danger');
- $document.on('click', '.js-confirm-danger', function (e) {
- var btn = $(e.target);
- var form = btn.closest('form');
- var text = btn.data('confirm-danger-message');
+
+ $document.on('click', '.js-confirm-danger', (e) => {
+ const btn = $(e.target);
+ const form = btn.closest('form');
+ const text = btn.data('confirm-danger-message');
e.preventDefault();
- return new ConfirmDangerModal(form, text);
- });
- $('input[type="search"]').each(function () {
- var $this = $(this);
- $this.attr('value', $this.val());
- });
- $document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function () {
- var $this;
- $this = $(this);
- return $this.attr('value', $this.val());
+
+ // eslint-disable-next-line no-new
+ new ConfirmDangerModal(form, text);
});
- $document.off('breakpoint:change').on('breakpoint:change', function (e, breakpoint) {
- var $gutterIcon;
+
+ $document.on('breakpoint:change', (e, breakpoint) => {
if (breakpoint === 'sm' || breakpoint === 'xs') {
- $gutterIcon = $sidebarGutterToggle.find('i');
+ const $gutterIcon = $sidebarGutterToggle.find('i');
if ($gutterIcon.hasClass('fa-angle-double-right')) {
- return $sidebarGutterToggle.trigger('click');
+ $sidebarGutterToggle.trigger('click');
}
}
});
- fitSidebarForSize = function () {
- var oldBootstrapBreakpoint;
- oldBootstrapBreakpoint = bootstrapBreakpoint;
+
+ function fitSidebarForSize() {
+ const oldBootstrapBreakpoint = bootstrapBreakpoint;
bootstrapBreakpoint = bp.getBreakpointSize();
+
if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
- return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
+ $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
}
- };
- $window.off('resize.app').on('resize.app', function () {
- return fitSidebarForSize();
- });
- loadAwardsHandler();
+ }
- renderTimeago();
+ $window.on('resize.app', fitSidebarForSize);
+
+ loadAwardsHandler();
- $('form.filter-form').on('submit', function (event) {
+ $('form.filter-form').on('submit', function filterFormSubmitCallback(event) {
const link = document.createElement('a');
link.href = this.action;
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index cb3cdea8111..bedd50de1bb 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
import 'vendor/jquery.waitforimages';
+import { __ } from '~/locale';
import TaskList from './task_list';
import MergeRequestTabs from './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
@@ -110,22 +111,22 @@ MergeRequest.prototype.initCommitMessageListeners = function() {
});
};
-MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
+MergeRequest.setStatusBoxToMerged = function() {
$('.detail-page-header .status-box')
- .removeClass(classToRemove)
- .addClass(classToAdd)
+ .removeClass('status-box-open')
+ .addClass('status-box-mr-merged')
.find('span')
- .text(newStatusText);
+ .text(__('Merged'));
};
-MergeRequest.prototype.decreaseCounter = function(by = 1) {
- const $el = $('.nav-links .js-merge-counter');
+MergeRequest.decreaseCounter = function(by = 1) {
+ const $el = $('.js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
$el.text(addDelimiter(count));
};
-MergeRequest.prototype.hideCloseButton = function() {
+MergeRequest.hideCloseButton = function() {
const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) {
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index f76a998bf8c..dd6c6b854bc 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -1,5 +1,3 @@
-/* global Sortable */
-
import Flash from './flash';
export default class Milestone {
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index d0ec70f1fcf..3d09d24b6ab 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -1,6 +1,7 @@
<script>
/* global katex */
import marked from 'marked';
+ import sanitize from 'sanitize-html';
import Prompt from './prompt.vue';
const renderer = new marked.Renderer();
@@ -82,7 +83,12 @@
},
computed: {
markdown() {
- return marked(this.cell.source.join('').replace(/\\/g, '\\\\'));
+ return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
+ allowedTags: false,
+ allowedAttributes: {
+ '*': ['class'],
+ },
+ });
},
},
};
diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue
index ebba5954de9..0535ee7afa8 100644
--- a/app/assets/javascripts/notebook/cells/output/html.vue
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -1,4 +1,5 @@
<script>
+ import sanitize from 'sanitize-html';
import Prompt from '../prompt.vue';
export default {
@@ -11,12 +12,24 @@
required: true,
},
},
+ computed: {
+ sanitizedOutput() {
+ return sanitize(this.rawCode, {
+ allowedTags: sanitize.defaults.allowedTags.concat([
+ 'img', 'svg',
+ ]),
+ allowedAttributes: {
+ img: ['src'],
+ },
+ });
+ },
+ },
};
</script>
<template>
<div class="output">
<prompt />
- <div v-html="rawCode"></div>
+ <div v-html="sanitizedOutput"></div>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 1f18c196137..3c8452ac808 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -271,7 +271,7 @@ Please check your network connection and try again.`;
<div class="timeline-content timeline-content-form">
<form
ref="commentForm"
- class="new-note js-quick-submit common-note-form gfm-form js-main-target-form"
+ class="new-note common-note-form gfm-form js-main-target-form"
>
<div class="error-alert"></div>
@@ -301,7 +301,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea"
:disabled="isSubmitting"
placeholder="Write a comment or drag your files here..."
@keydown.up="editCurrentUserLastNote()"
- @keydown.meta.enter="handleSave()">
+ @keydown.meta.enter="handleSave()"
+ @keydown.ctrl.enter="handleSave()">
</textarea>
</markdown-field>
<div class="note-form-actions">
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index aeda3497715..d382a9bb642 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -155,6 +155,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
slot="textarea"
placeholder="Write a comment or drag your files here..."
@keydown.meta.enter="handleUpdate()"
+ @keydown.ctrl.enter="handleUpdate()"
@keydown.up="editMyLastNote()"
@keydown.esc="cancelHandler(true)">
</textarea>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index b28dda4904d..5b255d4a710 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -66,9 +66,7 @@
<template>
<div class="note-header-info">
<a :href="author.path">
- <span class="note-header-author-name">
- {{ author.name }}
- </span>
+ <span class="note-header-author-name">{{ author.name }}</span>
<span class="note-headline-light">
@{{ author.username }}
</span>
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index ff88083a4b4..857a6793fe3 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -1,3 +1,5 @@
+import _ from 'underscore';
+
export default function initBroadcastMessagesForm() {
$('input#broadcast_message_color').on('input', function onMessageColorInput() {
const previewColor = $(this).val();
diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
new file mode 100644
index 00000000000..555725cbe12
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
@@ -0,0 +1,47 @@
+<script>
+ import axios from '~/lib/utils/axios_utils';
+ import Flash from '~/flash';
+ import modal from '~/vue_shared/components/modal.vue';
+ import { s__ } from '~/locale';
+ import { redirectTo } from '~/lib/utils/url_utility';
+
+ export default {
+ components: {
+ modal,
+ },
+ props: {
+ url: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ text() {
+ return s__('AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running.');
+ },
+ },
+ methods: {
+ onSubmit() {
+ return axios.post(this.url)
+ .then((response) => {
+ // follow the rediect to refresh the page
+ redirectTo(response.request.responseURL);
+ })
+ .catch((error) => {
+ Flash(s__('AdminArea|Stopping jobs failed'));
+ throw error;
+ });
+ },
+ },
+ };
+</script>
+
+<template>
+ <modal
+ id="stop-jobs-modal"
+ :title="s__('AdminArea|Stop all jobs?')"
+ :text="text"
+ kind="danger"
+ :primary-button-label="s__('AdminArea|Stop jobs')"
+ @submit="onSubmit" />
+</template>
diff --git a/app/assets/javascripts/pages/admin/jobs/index/index.js b/app/assets/javascripts/pages/admin/jobs/index/index.js
new file mode 100644
index 00000000000..0e004bd9174
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/jobs/index/index.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+
+import Translate from '~/vue_shared/translate';
+
+import stopJobsModal from './components/stop_jobs_modal.vue';
+
+Vue.use(Translate);
+
+export default () => {
+ const stopJobsButton = document.getElementById('stop-jobs-button');
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#stop-jobs-modal',
+ components: {
+ stopJobsModal,
+ },
+ mounted() {
+ stopJobsButton.classList.remove('disabled');
+ },
+ render(createElement) {
+ return createElement('stop-jobs-modal', {
+ props: {
+ url: stopJobsButton.dataset.url,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/pages/constants.js b/app/assets/javascripts/pages/constants.js
new file mode 100644
index 00000000000..328b6541636
--- /dev/null
+++ b/app/assets/javascripts/pages/constants.js
@@ -0,0 +1,6 @@
+/* eslint-disable import/prefer-default-export */
+
+export const FILTERED_SEARCH = {
+ MERGE_REQUESTS: 'merge_requests',
+ ISSUES: 'issues',
+};
diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
new file mode 100644
index 00000000000..b7353669e65
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
@@ -0,0 +1,7 @@
+import projectSelect from '~/project_select';
+import initLegacyFilters from '~/init_legacy_filters';
+
+export default () => {
+ projectSelect();
+ initLegacyFilters();
+};
diff --git a/app/assets/javascripts/pages/groups/activity/index.js b/app/assets/javascripts/pages/groups/activity/index.js
new file mode 100644
index 00000000000..a3bd1b0f3e2
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/activity/index.js
@@ -0,0 +1,3 @@
+import Activities from '~/activities';
+
+export default new Activities();
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
new file mode 100644
index 00000000000..48e8c9550bf
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -0,0 +1,3 @@
+import groupAvatar from '~/group_avatar';
+
+export default groupAvatar;
diff --git a/app/assets/javascripts/pages/groups/group_members/index/index.js b/app/assets/javascripts/pages/groups/group_members/index/index.js
new file mode 100644
index 00000000000..29319b97ae2
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/group_members/index/index.js
@@ -0,0 +1,11 @@
+/* eslint-disable no-new */
+
+import memberExpirationDate from '~/member_expiration_date';
+import Members from '~/members';
+import UsersSelect from '~/users_select';
+
+export default () => {
+ memberExpirationDate();
+ new Members();
+ new UsersSelect();
+};
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
new file mode 100644
index 00000000000..78db543a64d
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -0,0 +1,8 @@
+import projectSelect from '~/project_select';
+import initFilteredSearch from '~/pages/search/init_filtered_search';
+import { FILTERED_SEARCH } from '~/pages/constants';
+
+export default () => {
+ initFilteredSearch(FILTERED_SEARCH.ISSUES);
+ projectSelect();
+};
diff --git a/app/assets/javascripts/pages/groups/labels/edit/index.js b/app/assets/javascripts/pages/groups/labels/edit/index.js
new file mode 100644
index 00000000000..8ff4b044ac7
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/labels/edit/index.js
@@ -0,0 +1,3 @@
+import Labels from '~/labels';
+
+export default new Labels();
diff --git a/app/assets/javascripts/pages/groups/labels/index/index.js b/app/assets/javascripts/pages/groups/labels/index/index.js
new file mode 100644
index 00000000000..018345fa112
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/labels/index/index.js
@@ -0,0 +1,3 @@
+import initLabels from '~/init_labels';
+
+export default initLabels;
diff --git a/app/assets/javascripts/pages/groups/labels/new/index.js b/app/assets/javascripts/pages/groups/labels/new/index.js
new file mode 100644
index 00000000000..8ff4b044ac7
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/labels/new/index.js
@@ -0,0 +1,3 @@
+import Labels from '~/labels';
+
+export default new Labels();
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
new file mode 100644
index 00000000000..9b3af4537e7
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -0,0 +1,8 @@
+import projectSelect from '~/project_select';
+import initFilteredSearch from '~/pages/search/init_filtered_search';
+import { FILTERED_SEARCH } from '~/pages/constants';
+
+export default () => {
+ initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
+ projectSelect();
+};
diff --git a/app/assets/javascripts/pages/groups/milestones/edit/index.js b/app/assets/javascripts/pages/groups/milestones/edit/index.js
new file mode 100644
index 00000000000..5c99c90e24d
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/milestones/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '../../../../shared/milestones/form';
+
+export default () => initForm(false);
diff --git a/app/assets/javascripts/pages/groups/milestones/new/index.js b/app/assets/javascripts/pages/groups/milestones/new/index.js
new file mode 100644
index 00000000000..5c99c90e24d
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/milestones/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '../../../../shared/milestones/form';
+
+export default () => initForm(false);
diff --git a/app/assets/javascripts/pages/groups/new/index.js b/app/assets/javascripts/pages/groups/new/index.js
new file mode 100644
index 00000000000..7850b90d3d2
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/new/index.js
@@ -0,0 +1,9 @@
+import BindInOut from '~/behaviors/bind_in_out';
+import Group from '~/group';
+import groupAvatar from '~/group_avatar';
+
+export default () => {
+ BindInOut.initAll();
+ new Group(); // eslint-disable-line no-new
+ groupAvatar();
+};
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
new file mode 100644
index 00000000000..c4691cd367c
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -0,0 +1,9 @@
+import SecretValues from '~/behaviors/secret_values';
+
+export default () => {
+ const secretVariableTable = document.querySelector('.js-secret-variable-table');
+ if (secretVariableTable) {
+ const secretVariableTableValues = new SecretValues(secretVariableTable);
+ secretVariableTableValues.init();
+ }
+};
diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js
new file mode 100644
index 00000000000..45e11b64306
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/show/index.js
@@ -0,0 +1,19 @@
+/* eslint-disable no-new */
+
+import NewGroupChild from '~/groups/new_group_child';
+import notificationsDropdown from '~/notifications_dropdown';
+import NotificationsForm from '~/notifications_form';
+import ProjectsList from '~/projects_list';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+
+export default () => {
+ const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
+ new ShortcutsNavigation();
+ new NotificationsForm();
+ notificationsDropdown();
+ new ProjectsList();
+
+ if (newGroupChildWrapper) {
+ new NewGroupChild(newGroupChildWrapper);
+ }
+};
diff --git a/app/assets/javascripts/pages/omniauth_callbacks/index.js b/app/assets/javascripts/pages/omniauth_callbacks/index.js
new file mode 100644
index 00000000000..54f4e56359a
--- /dev/null
+++ b/app/assets/javascripts/pages/omniauth_callbacks/index.js
@@ -0,0 +1,5 @@
+import initU2F from '../../shared/sessions/u2f';
+
+export default () => {
+ initU2F();
+};
diff --git a/app/assets/javascripts/pages/projects/blame/show/index.js b/app/assets/javascripts/pages/projects/blame/show/index.js
new file mode 100644
index 00000000000..480357a309c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/blame/show/index.js
@@ -0,0 +1,3 @@
+import initBlob from '~/pages/projects/init_blob';
+
+export default initBlob;
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
new file mode 100644
index 00000000000..a3eeb1cefb6
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -0,0 +1,7 @@
+import BlobViewer from '~/blob/viewer/index';
+import initBlob from '~/pages/projects/init_blob';
+
+export default () => {
+ new BlobViewer(); // eslint-disable-line no-new
+ initBlob();
+};
diff --git a/app/assets/javascripts/pages/projects/boards/index.js b/app/assets/javascripts/pages/projects/boards/index.js
new file mode 100644
index 00000000000..42c9bb5ec99
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/boards/index.js
@@ -0,0 +1,7 @@
+import UsersSelect from '~/users_select';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+
+export default () => {
+ new UsersSelect(); // eslint-disable-line no-new
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
new file mode 100644
index 00000000000..cee0f19bf2a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -0,0 +1,7 @@
+import AjaxLoadingSpinner from '~/ajax_loading_spinner';
+import DeleteModal from '~/branches/branches_delete_modal';
+
+export default () => {
+ AjaxLoadingSpinner.init();
+ new DeleteModal(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js
new file mode 100644
index 00000000000..ae5e033e97e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/branches/new/index.js
@@ -0,0 +1,3 @@
+import NewBranchForm from '~/new_branch_form';
+
+export default () => new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js
new file mode 100644
index 00000000000..d531ab81dc7
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/clusters/index/index.js
@@ -0,0 +1,5 @@
+import ClustersIndex from '~/clusters/clusters_index';
+
+export default () => {
+ new ClustersIndex(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js
new file mode 100644
index 00000000000..0458c02a66f
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/clusters/show/index.js
@@ -0,0 +1,5 @@
+import ClustersBundle from '~/clusters/clusters_bundle';
+
+export default () => {
+ new ClustersBundle(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
new file mode 100644
index 00000000000..523ad567021
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
@@ -0,0 +1,8 @@
+import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
+
+export default () => {
+ new MiniPipelineGraph({
+ container: '.js-commit-pipeline-graph',
+ }).bindEvents();
+ $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+};
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
new file mode 100644
index 00000000000..5ac38e6f278
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -0,0 +1,22 @@
+/* eslint-disable no-new */
+import Diff from '~/diff';
+import ZenMode from '~/zen_mode';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
+import initNotes from '~/init_notes';
+import initChangesDropdown from '~/init_changes_dropdown';
+import { fetchCommitMergeRequests } from '~/commit_merge_requests';
+
+export default () => {
+ new Diff();
+ new ZenMode();
+ new ShortcutsNavigation();
+ new MiniPipelineGraph({
+ container: '.js-commit-pipeline-graph',
+ }).bindEvents();
+ initNotes();
+ const stickyBarPaddingTop = 16;
+ initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
+ $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ fetchCommitMergeRequests();
+};
diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js
new file mode 100644
index 00000000000..90b5882a24f
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/commits/show/index.js
@@ -0,0 +1,9 @@
+import CommitsList from '~/commits';
+import GpgBadges from '~/gpg_badges';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+
+export default () => {
+ CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ GpgBadges.fetch();
+};
diff --git a/app/assets/javascripts/pages/projects/compare/index.js b/app/assets/javascripts/pages/projects/compare/index.js
new file mode 100644
index 00000000000..890062eeee6
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/compare/index.js
@@ -0,0 +1,5 @@
+import initCompareAutocomplete from '~/compare_autocomplete';
+
+export default () => {
+ initCompareAutocomplete();
+};
diff --git a/app/assets/javascripts/pages/projects/compare/show/index.js b/app/assets/javascripts/pages/projects/compare/show/index.js
new file mode 100644
index 00000000000..6b8d4503568
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/compare/show/index.js
@@ -0,0 +1,8 @@
+import Diff from '~/diff';
+import initChangesDropdown from '~/init_changes_dropdown';
+
+export default () => {
+ new Diff(); // eslint-disable-line no-new
+ const paddingTop = 16;
+ initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
+};
diff --git a/app/assets/javascripts/pages/projects/constants.js b/app/assets/javascripts/pages/projects/constants.js
new file mode 100644
index 00000000000..9efbf7cd36e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/constants.js
@@ -0,0 +1,6 @@
+/* eslint-disable import/prefer-default-export */
+
+export const ISSUABLE_INDEX = {
+ MERGE_REQUEST: 'merge_request_',
+ ISSUE: 'issue_',
+};
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
new file mode 100644
index 00000000000..9edf36d66b1
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -0,0 +1,14 @@
+import initSettingsPanels from '~/settings_panels';
+import setupProjectEdit from '~/project_edit';
+import ProjectNew from '../shared/project_new';
+import projectAvatar from '../shared/project_avatar';
+import initProjectPermissionsSettings from '../shared/permissions';
+
+export default () => {
+ new ProjectNew(); // eslint-disable-line no-new
+ setupProjectEdit();
+ // Initialize expandable settings panels
+ initSettingsPanels();
+ projectAvatar();
+ initProjectPermissionsSettings();
+};
diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js
new file mode 100644
index 00000000000..42bde0ff779
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/find_file/show/index.js
@@ -0,0 +1,12 @@
+import ProjectFindFile from '~/project_find_file';
+import ShortcutsFindFile from '~/shortcuts_find_file';
+
+export default () => {
+ const findElement = document.querySelector('.js-file-finder');
+ const projectFindFile = new ProjectFindFile($('.file-finder-holder'), {
+ url: findElement.dataset.fileFindUrl,
+ treeUrl: findElement.dataset.findTreeUrl,
+ blobUrlTemplate: findElement.dataset.blobUrlTemplate,
+ });
+ new ShortcutsFindFile(projectFindFile); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/forks/new/index.js b/app/assets/javascripts/pages/projects/forks/new/index.js
new file mode 100644
index 00000000000..7825eb01949
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/forks/new/index.js
@@ -0,0 +1,5 @@
+import ProjectFork from '~/project_fork';
+
+export default () => {
+ new ProjectFork(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/imports/show/index.js b/app/assets/javascripts/pages/projects/imports/show/index.js
new file mode 100644
index 00000000000..378f7b3f38b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/imports/show/index.js
@@ -0,0 +1,5 @@
+import ProjectImport from '~/project_import';
+
+export default () => {
+ new ProjectImport(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
new file mode 100644
index 00000000000..9b1d52692a3
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -0,0 +1,7 @@
+import Project from './project';
+import ShortcutsNavigation from '../../shortcuts_navigation';
+
+export default () => {
+ new Project(); // eslint-disable-line no-new
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
new file mode 100644
index 00000000000..26f0ad46114
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -0,0 +1,33 @@
+import LineHighlighter from '~/line_highlighter';
+import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsBlob from '~/shortcuts_blob';
+import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
+
+export default () => {
+ new LineHighlighter(); // eslint-disable-line no-new
+
+ new BlobLinePermalinkUpdater( // eslint-disable-line no-new
+ document.querySelector('#blob-content-holder'),
+ '.diff-line-num[data-line-number]',
+ document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
+ );
+
+ const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
+ const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
+
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+
+ new ShortcutsBlob({ // eslint-disable-line no-new
+ skipResetBindings: true,
+ fileBlobPermalinkUrl,
+ });
+
+ new BlobForkSuggestion({ // eslint-disable-line no-new
+ openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'),
+ forkButtons: document.querySelectorAll('.js-fork-suggestion-button'),
+ cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
+ suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
+ actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
+ }).init();
+};
diff --git a/app/assets/javascripts/pages/projects/init_form.js b/app/assets/javascripts/pages/projects/init_form.js
new file mode 100644
index 00000000000..0b6c5c1d30b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/init_form.js
@@ -0,0 +1,7 @@
+import ZenMode from '~/zen_mode';
+import GLForm from '~/gl_form';
+
+export default function ($formEl) {
+ new ZenMode(); // eslint-disable-line no-new
+ new GLForm($formEl, true); // eslint-disable-line no-new
+}
diff --git a/app/assets/javascripts/pages/projects/issues/edit/index.js b/app/assets/javascripts/pages/projects/issues/edit/index.js
new file mode 100644
index 00000000000..7f27f379d8c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/edit/index.js
@@ -0,0 +1,5 @@
+import initForm from '../form';
+
+export default () => {
+ initForm();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
new file mode 100644
index 00000000000..5c7daf84738
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -0,0 +1,16 @@
+/* eslint-disable no-new */
+import GLForm from '~/gl_form';
+import IssuableForm from '~/issuable_form';
+import LabelsSelect from '~/labels_select';
+import MilestoneSelect from '~/milestone_select';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
+
+export default () => {
+ new ShortcutsNavigation();
+ new GLForm($('.issue-form'), true);
+ new IssuableForm($('.issue-form'));
+ new LabelsSelect();
+ new MilestoneSelect();
+ new IssuableTemplateSelectors();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
new file mode 100644
index 00000000000..0d3f35f044d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -0,0 +1,16 @@
+/* eslint-disable no-new */
+
+import IssuableIndex from '~/issuable_index';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import UsersSelect from '~/users_select';
+import initFilteredSearch from '~/pages/search/init_filtered_search';
+import { FILTERED_SEARCH } from '~/pages/constants';
+import { ISSUABLE_INDEX } from '~/pages/projects/constants';
+
+export default () => {
+ initFilteredSearch(FILTERED_SEARCH.ISSUES);
+ new IssuableIndex(ISSUABLE_INDEX.ISSUE);
+
+ new ShortcutsNavigation();
+ new UsersSelect();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/new/index.js b/app/assets/javascripts/pages/projects/issues/new/index.js
new file mode 100644
index 00000000000..7f27f379d8c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/new/index.js
@@ -0,0 +1,5 @@
+import initForm from '../form';
+
+export default () => {
+ initForm();
+};
diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js
new file mode 100644
index 00000000000..48ed8fb2243
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/show/index.js
@@ -0,0 +1,13 @@
+
+/* eslint-disable no-new */
+import initIssuableSidebar from '~/init_issuable_sidebar';
+import Issue from '~/issue';
+import ShortcutsIssuable from '~/shortcuts_issuable';
+import ZenMode from '~/zen_mode';
+
+export default () => {
+ new Issue();
+ new ShortcutsIssuable();
+ new ZenMode();
+ initIssuableSidebar();
+};
diff --git a/app/assets/javascripts/pages/projects/labels/edit/index.js b/app/assets/javascripts/pages/projects/labels/edit/index.js
new file mode 100644
index 00000000000..72c5e4744ac
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/edit/index.js
@@ -0,0 +1,3 @@
+import Labels from '~/labels';
+
+export default () => new Labels();
diff --git a/app/assets/javascripts/pages/projects/labels/index/index.js b/app/assets/javascripts/pages/projects/labels/index/index.js
new file mode 100644
index 00000000000..018345fa112
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/index/index.js
@@ -0,0 +1,3 @@
+import initLabels from '~/init_labels';
+
+export default initLabels;
diff --git a/app/assets/javascripts/pages/projects/labels/new/index.js b/app/assets/javascripts/pages/projects/labels/new/index.js
new file mode 100644
index 00000000000..72c5e4744ac
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/labels/new/index.js
@@ -0,0 +1,3 @@
+import Labels from '~/labels';
+
+export default () => new Labels();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js
new file mode 100644
index 00000000000..734d01ae6f2
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js
@@ -0,0 +1,3 @@
+import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request';
+
+export default initMergeRequest;
diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
new file mode 100644
index 00000000000..ccd0b54c5ed
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
@@ -0,0 +1,18 @@
+import Compare from '~/compare';
+import MergeRequest from '~/merge_request';
+
+export default () => {
+ const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
+ if (mrNewCompareNode) {
+ new Compare({ // eslint-disable-line no-new
+ targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl,
+ sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl,
+ targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl,
+ });
+ } else {
+ const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit');
+ new MergeRequest({ // eslint-disable-line no-new
+ action: mrNewSubmitNode.dataset.mrSubmitAction,
+ });
+ }
+};
diff --git a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js
new file mode 100644
index 00000000000..734d01ae6f2
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js
@@ -0,0 +1,3 @@
+import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request';
+
+export default initMergeRequest;
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
new file mode 100644
index 00000000000..b386e8fb48d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -0,0 +1,13 @@
+import IssuableIndex from '~/issuable_index';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import UsersSelect from '~/users_select';
+import initFilteredSearch from '~/pages/search/init_filtered_search';
+import { FILTERED_SEARCH } from '~/pages/constants';
+import { ISSUABLE_INDEX } from '~/pages/projects/constants';
+
+export default () => {
+ initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
+ new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ new UsersSelect(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
new file mode 100644
index 00000000000..8bfac606aab
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -0,0 +1,19 @@
+/* eslint-disable no-new */
+
+import Diff from '~/diff';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import GLForm from '~/gl_form';
+import IssuableForm from '~/issuable_form';
+import LabelsSelect from '~/labels_select';
+import MilestoneSelect from '~/milestone_select';
+import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
+
+export default () => {
+ new Diff();
+ new ShortcutsNavigation();
+ new GLForm($('.merge-request-form'), true);
+ new IssuableForm($('.merge-request-form'));
+ new LabelsSelect();
+ new MilestoneSelect();
+ new IssuableTemplateSelectors();
+};
diff --git a/app/assets/javascripts/pages/projects/merge_requests/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
index 23c7f0a2c8a..7bd8abf0574 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/show/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
@@ -13,7 +13,7 @@ export default () => {
initNotes();
const mrShowNode = document.querySelector('.merge-request');
- window.mergeRequest = new MergeRequest({ // eslint-disable-line no-new
+ new MergeRequest({ // eslint-disable-line no-new
action: mrShowNode.dataset.mrAction,
});
diff --git a/app/assets/javascripts/pages/projects/milestones/edit/index.js b/app/assets/javascripts/pages/projects/milestones/edit/index.js
new file mode 100644
index 00000000000..10e3979a36e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/milestones/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '../../../../shared/milestones/form';
+
+export default () => initForm();
diff --git a/app/assets/javascripts/pages/projects/milestones/new/index.js b/app/assets/javascripts/pages/projects/milestones/new/index.js
new file mode 100644
index 00000000000..10e3979a36e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/milestones/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '../../../../shared/milestones/form';
+
+export default () => initForm();
diff --git a/app/assets/javascripts/pages/projects/new/index.js b/app/assets/javascripts/pages/projects/new/index.js
new file mode 100644
index 00000000000..71c49deb9d0
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/new/index.js
@@ -0,0 +1,9 @@
+import ProjectNew from '../shared/project_new';
+import initProjectVisibilitySelector from '../../../project_visibility';
+import initProjectNew from '../../../projects/project_new';
+
+export default () => {
+ new ProjectNew(); // eslint-disable-line no-new
+ initProjectVisibilitySelector();
+ initProjectNew.bindEvents();
+};
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/pages/projects/project.js
index d4f26b81f30..e30d558726b 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -1,8 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
import Cookies from 'js-cookie';
-import { visitUrl } from './lib/utils/url_utility';
-import projectSelect from './project_select';
+import { visitUrl } from '../../lib/utils/url_utility';
+import projectSelect from '../../project_select';
export default class Project {
constructor() {
diff --git a/app/assets/javascripts/pages/projects/releases/edit/index.js b/app/assets/javascripts/pages/projects/releases/edit/index.js
new file mode 100644
index 00000000000..3d997cdfff0
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/releases/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '~/pages/projects/init_form';
+
+export default initForm($('.release-form'));
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
new file mode 100644
index 00000000000..94b927a1548
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -0,0 +1,18 @@
+import initSettingsPanels from '~/settings_panels';
+import SecretValues from '~/behaviors/secret_values';
+
+export default function () {
+ // Initialize expandable settings panels
+ initSettingsPanels();
+ const runnerToken = document.querySelector('.js-secret-runner-token');
+ if (runnerToken) {
+ const runnerTokenSecretValue = new SecretValues(runnerToken);
+ runnerTokenSecretValue.init();
+ }
+
+ const secretVariableTable = document.querySelector('.js-secret-variable-table');
+ if (secretVariableTable) {
+ const secretVariableTableValues = new SecretValues(secretVariableTable);
+ secretVariableTableValues.init();
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
new file mode 100644
index 00000000000..83b5467fbc0
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -0,0 +1,3 @@
+import initSettingsPanels from '~/settings_panels';
+
+export default initSettingsPanels;
diff --git a/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
index 3ebfe82597a..9b13b2a524f 100644
--- a/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
@@ -1,5 +1,5 @@
<script>
- import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
+ import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
export default {
components: {
diff --git a/app/assets/javascripts/projects/permissions/components/project_setting_row.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
index 25a88f846eb..25a88f846eb 100644
--- a/app/assets/javascripts/projects/permissions/components/project_setting_row.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
diff --git a/app/assets/javascripts/projects/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index c96ce12d9fb..380c6c3ea7d 100644
--- a/app/assets/javascripts/projects/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -1,6 +1,6 @@
<script>
import projectFeatureSetting from './project_feature_setting.vue';
- import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
+ import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
import projectSettingRow from './project_setting_row.vue';
import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
import { toggleHiddenClassBySelector } from '../external';
diff --git a/app/assets/javascripts/projects/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
index ce47562f259..ce47562f259 100644
--- a/app/assets/javascripts/projects/permissions/constants.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
diff --git a/app/assets/javascripts/projects/permissions/external.js b/app/assets/javascripts/pages/projects/shared/permissions/external.js
index 460af4a2111..460af4a2111 100644
--- a/app/assets/javascripts/projects/permissions/external.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/external.js
diff --git a/app/assets/javascripts/projects/permissions/index.js b/app/assets/javascripts/pages/projects/shared/permissions/index.js
index dbde8dda634..dbde8dda634 100644
--- a/app/assets/javascripts/projects/permissions/index.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/index.js
diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/pages/projects/shared/project_avatar.js
index 56627aa155c..56627aa155c 100644
--- a/app/assets/javascripts/project_avatar.js
+++ b/app/assets/javascripts/pages/projects/shared/project_avatar.js
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/pages/projects/shared/project_new.js
index ca548d011b6..86faba0b910 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/pages/projects/shared/project_new.js
@@ -1,6 +1,6 @@
/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
-import VisibilitySelect from './visibility_select';
+import VisibilitySelect from '../../../visibility_select';
function highlightChanges($elm) {
$elm.addClass('highlight-changes');
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
new file mode 100644
index 00000000000..92dc1e59651
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -0,0 +1,23 @@
+import ShortcutsNavigation from '~/shortcuts_navigation';
+import NotificationsForm from '~/notifications_form';
+import UserCallout from '~/user_callout';
+import TreeView from '~/tree';
+import BlobViewer from '~/blob/viewer/index';
+import Activities from '~/activities';
+import { ajaxGet } from '~/lib/utils/common_utils';
+
+export default () => {
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ new NotificationsForm(); // eslint-disable-line no-new
+ new UserCallout({ // eslint-disable-line no-new
+ setCalloutPerProject: true,
+ className: 'js-autodevops-banner',
+ });
+
+ if ($('#tree-slider').length) new TreeView(); // eslint-disable-line no-new
+ if ($('.blob-viewer').length) new BlobViewer(); // eslint-disable-line no-new
+ if ($('.project-show-activity').length) new Activities(); // eslint-disable-line no-new
+ $('#tree-slider').waitForImages(() => {
+ ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
+ });
+};
diff --git a/app/assets/javascripts/pages/projects/snippets/edit/index.js b/app/assets/javascripts/pages/projects/snippets/edit/index.js
new file mode 100644
index 00000000000..9edb16dc73b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/snippets/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '~/pages/projects/init_form';
+
+export default initForm($('.snippet-form'));
diff --git a/app/assets/javascripts/pages/projects/snippets/new/index.js b/app/assets/javascripts/pages/projects/snippets/new/index.js
new file mode 100644
index 00000000000..9edb16dc73b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/snippets/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '~/pages/projects/init_form';
+
+export default initForm($('.snippet-form'));
diff --git a/app/assets/javascripts/pages/projects/snippets/show/index.js b/app/assets/javascripts/pages/projects/snippets/show/index.js
new file mode 100644
index 00000000000..d8cf5184f8f
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/snippets/show/index.js
@@ -0,0 +1,7 @@
+import initNotes from '~/init_notes';
+import ZenMode from '~/zen_mode';
+
+export default function () {
+ initNotes();
+ new ZenMode(); // eslint-disable-line no-new
+}
diff --git a/app/assets/javascripts/pages/projects/tags/new/index.js b/app/assets/javascripts/pages/projects/tags/new/index.js
new file mode 100644
index 00000000000..dacc2875c8c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/tags/new/index.js
@@ -0,0 +1,9 @@
+import RefSelectDropdown from '../../../../ref_select_dropdown';
+import ZenMode from '../../../../zen_mode';
+import GLForm from '../../../../gl_form';
+
+export default () => {
+ new ZenMode(); // eslint-disable-line no-new
+ new GLForm($('.tag-form'), true); // eslint-disable-line no-new
+ new RefSelectDropdown($('.js-branch-select')); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
new file mode 100644
index 00000000000..28a0160f47d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -0,0 +1,15 @@
+import TreeView from '../../../../tree';
+import ShortcutsNavigation from '../../../../shortcuts_navigation';
+import BlobViewer from '../../../../blob/viewer';
+import NewCommitForm from '../../../../new_commit_form';
+import { ajaxGet } from '../../../../lib/utils/common_utils';
+
+export default () => {
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ new TreeView(); // eslint-disable-line no-new
+ new BlobViewer(); // eslint-disable-line no-new
+ new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
+ $('#tree-slider').waitForImages(() =>
+ ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
+};
+
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
new file mode 100644
index 00000000000..eb14c7a0e78
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -0,0 +1,11 @@
+import Wikis from './wikis';
+import ShortcutsWiki from '../../../shortcuts_wiki';
+import ZenMode from '../../../zen_mode';
+import GLForm from '../../../gl_form';
+
+export default () => {
+ new Wikis(); // eslint-disable-line no-new
+ new ShortcutsWiki(); // eslint-disable-line no-new
+ new ZenMode(); // eslint-disable-line no-new
+ new GLForm($('.wiki-form'), true); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js
index 7a865587444..34a12ef76a1 100644
--- a/app/assets/javascripts/wikis.js
+++ b/app/assets/javascripts/pages/projects/wikis/wikis.js
@@ -1,5 +1,5 @@
-import bp from './breakpoints';
-import { slugify } from './lib/utils/text_utility';
+import bp from '../../../breakpoints';
+import { slugify } from '../../../lib/utils/text_utility';
export default class Wikis {
constructor() {
diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js
new file mode 100644
index 00000000000..44853636aea
--- /dev/null
+++ b/app/assets/javascripts/pages/search/init_filtered_search.js
@@ -0,0 +1,7 @@
+export default (page) => {
+ const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
+ if (filteredSearchEnabled) {
+ const filteredSearchManager = new gl.FilteredSearchManager(page);
+ filteredSearchManager.setup();
+ }
+};
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index d44195f6b72..dc621bc87c0 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -15,6 +15,7 @@ export default class Search {
$groupDropdown.glDropdown({
selectable: true,
filterable: true,
+ filterRemote: true,
fieldName: 'group_id',
search: {
fields: ['full_name'],
@@ -43,6 +44,7 @@ export default class Search {
$projectDropdown.glDropdown({
selectable: true,
filterable: true,
+ filterRemote: true,
fieldName: 'project_id',
search: {
fields: ['name'],
diff --git a/app/assets/javascripts/pages/sessions/index.js b/app/assets/javascripts/pages/sessions/index.js
new file mode 100644
index 00000000000..54f4e56359a
--- /dev/null
+++ b/app/assets/javascripts/pages/sessions/index.js
@@ -0,0 +1,5 @@
+import initU2F from '../../shared/sessions/u2f';
+
+export default () => {
+ initU2F();
+};
diff --git a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
index f99573e5c74..08f0afdcce3 100644
--- a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
@@ -1,5 +1,3 @@
-/* eslint no-param-reassign: ["error", { "props": false }]*/
-/* eslint no-new: "off" */
import AccessorUtilities from '~/lib/utils/accessor';
/**
@@ -11,6 +9,10 @@ export default class SigninTabsMemoizer {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
+ // sets selected tab if given as hash tag
+ if (window.location.hash) {
+ this.saveData(window.location.hash);
+ }
this.bootstrap();
}
diff --git a/app/assets/javascripts/pages/snippets/edit/index.js b/app/assets/javascripts/pages/snippets/edit/index.js
new file mode 100644
index 00000000000..9c664b5f1ff
--- /dev/null
+++ b/app/assets/javascripts/pages/snippets/edit/index.js
@@ -0,0 +1,3 @@
+import form from '../form';
+
+export default form;
diff --git a/app/assets/javascripts/pages/snippets/form.js b/app/assets/javascripts/pages/snippets/form.js
new file mode 100644
index 00000000000..f996d3cd74e
--- /dev/null
+++ b/app/assets/javascripts/pages/snippets/form.js
@@ -0,0 +1,7 @@
+import GLForm from '~/gl_form';
+import ZenMode from '~/zen_mode';
+
+export default () => {
+ new GLForm($('.snippet-form'), false); // eslint-disable-line no-new
+ new ZenMode(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/snippets/new/index.js b/app/assets/javascripts/pages/snippets/new/index.js
new file mode 100644
index 00000000000..9c664b5f1ff
--- /dev/null
+++ b/app/assets/javascripts/pages/snippets/new/index.js
@@ -0,0 +1,3 @@
+import form from '../form';
+
+export default form;
diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue
index 4ad3f66ee8c..77553ca67cc 100644
--- a/app/assets/javascripts/pipelines/components/async_button.vue
+++ b/app/assets/javascripts/pipelines/components/async_button.vue
@@ -3,6 +3,7 @@
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
@@ -11,6 +12,7 @@
},
components: {
loadingIcon,
+ icon,
},
props: {
endpoint: {
@@ -41,9 +43,6 @@
};
},
computed: {
- iconClass() {
- return `fa fa-${this.icon}`;
- },
buttonClass() {
return `btn ${this.cssClass}`;
},
@@ -76,10 +75,9 @@
data-container="body"
data-placement="top"
:disabled="isLoading">
- <i
- :class="iconClass"
- aria-hidden="true">
- </i>
+ <icon
+ :name="icon"
+ />
<loading-icon v-if="isLoading" />
</button>
</template>
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index 942acc8c412..e08c2092680 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -92,6 +92,7 @@
<loading-icon
v-if="isLoading"
size="2"
+ class="prepend-top-default append-bottom-default"
/>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index efda36c12d6..3297af7bde4 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -1,7 +1,7 @@
<script>
- import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
@@ -10,6 +10,7 @@
},
components: {
loadingIcon,
+ icon,
},
props: {
actions: {
@@ -19,7 +20,6 @@
},
data() {
return {
- playIconSvg,
isLoading: false,
};
},
@@ -52,7 +52,10 @@
aria-label="Manual job"
:disabled="isLoading"
>
- <span v-html="playIconSvg"></span>
+ <icon
+ name="play"
+ class="icon-play"
+ />
<i
class="fa fa-caret-down"
aria-hidden="true">
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index c6638cdcf1e..6681b89e629 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -50,13 +50,13 @@
Pipeline
</div>
<div
- class="table-section section-25 js-pipeline-commit pipeline-commit"
+ class="table-section section-20 js-pipeline-commit pipeline-commit"
role="rowheader"
>
Commit
</div>
<div
- class="table-section section-15 js-pipeline-stages pipeline-stages"
+ class="table-section section-20 js-pipeline-stages pipeline-stages"
role="rowheader"
>
Stages
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 670b777199c..d0e4cf7ff40 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -239,7 +239,7 @@
:auto-devops-help-path="autoDevopsHelpPath"
/>
- <div class="table-section section-25">
+ <div class="table-section section-20">
<div
class="table-mobile-header"
role="rowheader">
@@ -258,7 +258,7 @@
</div>
</div>
- <div class="table-section section-wrap section-15 stage-cell">
+ <div class="table-section section-wrap section-20 stage-cell">
<div
class="table-mobile-header"
role="rowheader">
@@ -312,7 +312,7 @@
:endpoint="pipeline.cancel_path"
css-class="js-pipelines-cancel-button btn-remove"
title="Cancel"
- icon="remove"
+ icon="close"
confirm-action-message="Are you sure you want to cancel this pipeline?"
/>
</div>
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 4710e70d619..f5133111d04 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -100,8 +100,6 @@ const bindEvents = () => {
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
};
-document.addEventListener('DOMContentLoaded', bindEvents);
-
export default {
bindEvents,
deriveProjectPathFromUrl,
diff --git a/app/assets/javascripts/projects_dropdown/index.js b/app/assets/javascripts/projects_dropdown/index.js
index 2660da3c558..e78ebce2923 100644
--- a/app/assets/javascripts/projects_dropdown/index.js
+++ b/app/assets/javascripts/projects_dropdown/index.js
@@ -19,11 +19,8 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
- $(navEl).on('show.bs.dropdown', (e) => {
- const dropdownEl = $(e.currentTarget).find('.projects-dropdown-menu');
- dropdownEl.one('transitionend', () => {
- eventHub.$emit('dropdownOpen');
- });
+ $(navEl).on('shown.bs.dropdown', () => {
+ eventHub.$emit('dropdownOpen');
});
// eslint-disable-next-line no-new
diff --git a/app/assets/javascripts/projects_dropdown/service/projects_service.js b/app/assets/javascripts/projects_dropdown/service/projects_service.js
index 9cbd8f21f2a..7231f520933 100644
--- a/app/assets/javascripts/projects_dropdown/service/projects_service.js
+++ b/app/assets/javascripts/projects_dropdown/service/projects_service.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import VueResource from 'vue-resource';
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 0a9fdb074e5..2948baeab11 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
@@ -1,6 +1,6 @@
import _ from 'underscore';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
-import ProtectedBranchDropdown from './protected_branch_dropdown';
+import CreateItemDropdown from '../create_item_dropdown';
import AccessorUtilities from '../lib/utils/accessor';
const PB_LOCAL_STORAGE_KEY = 'protected-branches-defaults';
@@ -35,10 +35,12 @@ export default class ProtectedBranchCreate {
onSelect: this.onSelectCallback,
});
- // Protected branch dropdown
- this.protectedBranchDropdown = new ProtectedBranchDropdown({
+ this.createItemDropdown = new CreateItemDropdown({
$dropdown: $protectedBranchDropdown,
+ defaultToggleLabel: 'Protected Branch',
+ fieldName: 'protected_branch[name]',
onSelect: this.onSelectCallback,
+ getData: ProtectedBranchCreate.getProtectedBranches,
});
this.loadPreviousSelection($allowedToMergeDropdown.data('glDropdown'), $allowedToPushDropdown.data('glDropdown'));
@@ -60,6 +62,10 @@ export default class ProtectedBranchCreate {
this.$form.find('input[type="submit"]').attr('disabled', completedForm);
}
+ static getProtectedBranches(term, callback) {
+ callback(gon.open_branches);
+ }
+
loadPreviousSelection(mergeDropdown, pushDropdown) {
let mergeIndex = 0;
let pushIndex = 0;
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
deleted file mode 100644
index 678882a8d2c..00000000000
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import _ from 'underscore';
-
-export default class ProtectedBranchDropdown {
- /**
- * @param {Object} options containing
- * `$dropdown` target element
- * `onSelect` event callback
- * $dropdown must be an element created using `dropdown_branch()` rails helper
- */
- constructor(options) {
- this.onSelect = options.onSelect;
- this.$dropdown = options.$dropdown;
- this.$dropdownContainer = this.$dropdown.parent();
- this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
- this.$protectedBranch = this.$dropdownContainer.find('.js-create-new-protected-branch');
-
- this.buildDropdown();
- this.bindEvents();
-
- // Hide footer
- this.toggleFooter(true);
- }
-
- buildDropdown() {
- this.$dropdown.glDropdown({
- data: this.getProtectedBranches.bind(this),
- filterable: true,
- remote: false,
- search: {
- fields: ['title'],
- },
- selectable: true,
- toggleLabel(selected) {
- return (selected && 'id' in selected) ? selected.title : 'Protected Branch';
- },
- fieldName: 'protected_branch[name]',
- text(protectedBranch) {
- return _.escape(protectedBranch.title);
- },
- id(protectedBranch) {
- return _.escape(protectedBranch.id);
- },
- onFilter: this.toggleCreateNewButton.bind(this),
- clicked: (options) => {
- options.e.preventDefault();
- this.onSelect();
- },
- });
- }
-
- bindEvents() {
- this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
- }
-
- onClickCreateWildcard(e) {
- e.preventDefault();
-
- // Refresh the dropdown's data, which ends up calling `getProtectedBranches`
- this.$dropdown.data('glDropdown').remote.execute();
- this.$dropdown.data('glDropdown').selectRowAtIndex();
- }
-
- getProtectedBranches(term, callback) {
- if (this.selectedBranch) {
- callback(gon.open_branches.concat(this.selectedBranch));
- } else {
- callback(gon.open_branches);
- }
- }
-
- toggleCreateNewButton(branchName) {
- if (branchName) {
- this.selectedBranch = {
- title: branchName,
- id: branchName,
- text: branchName,
- };
-
- this.$dropdownContainer
- .find('.js-create-new-protected-branch code')
- .text(branchName);
- }
-
- this.toggleFooter(!branchName);
- }
-
- toggleFooter(toggleState) {
- this.$dropdownFooter.toggleClass('hidden', toggleState);
- }
-}
diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js
index 91bd140bd12..d1e4a75c17b 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_create.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_create.js
@@ -1,5 +1,5 @@
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
-import ProtectedTagDropdown from './protected_tag_dropdown';
+import CreateItemDropdown from '../create_item_dropdown';
export default class ProtectedTagCreate {
constructor() {
@@ -24,9 +24,12 @@ export default class ProtectedTagCreate {
$allowedToCreateDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected tag dropdown
- this.protectedTagDropdown = new ProtectedTagDropdown({
+ this.createItemDropdown = new CreateItemDropdown({
$dropdown: this.$form.find('.js-protected-tag-select'),
+ defaultToggleLabel: 'Protected Tag',
+ fieldName: 'protected_tag[name]',
onSelect: this.onSelectCallback,
+ getData: ProtectedTagCreate.getProtectedTags,
});
}
@@ -38,4 +41,8 @@ export default class ProtectedTagCreate {
this.$form.find('input[type="submit"]').attr('disabled', !($tagInput.val() && $allowedToCreateInput.length));
}
+
+ static getProtectedTags(term, callback) {
+ callback(gon.open_tags);
+ }
}
diff --git a/app/assets/javascripts/render_mermaid.js b/app/assets/javascripts/render_mermaid.js
index b7cde6fb092..31c7a772cf4 100644
--- a/app/assets/javascripts/render_mermaid.js
+++ b/app/assets/javascripts/render_mermaid.js
@@ -19,7 +19,11 @@ export default function renderMermaid($els) {
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
mermaid.initialize({
- loadOnStart: false,
+ // mermaid core options
+ mermaid: {
+ startOnLoad: false,
+ },
+ // mermaidAPI options
theme: 'neutral',
});
diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js
new file mode 100644
index 00000000000..db466f722c4
--- /dev/null
+++ b/app/assets/javascripts/shared/milestones/form.js
@@ -0,0 +1,9 @@
+import ZenMode from '../../zen_mode';
+import DueDateSelectors from '../../due_date_select';
+import GLForm from '../../gl_form';
+
+export default (initGFM = true) => {
+ new ZenMode(); // eslint-disable-line no-new
+ new DueDateSelectors(); // eslint-disable-line no-new
+ new GLForm($('.milestone-form'), initGFM); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/shared/sessions/u2f.js b/app/assets/javascripts/shared/sessions/u2f.js
new file mode 100644
index 00000000000..1d075f7e872
--- /dev/null
+++ b/app/assets/javascripts/shared/sessions/u2f.js
@@ -0,0 +1,16 @@
+import U2FAuthenticate from '../../u2f/authenticate';
+
+export default () => {
+ if (!gon.u2f) return;
+
+ const u2fAuthenticate = new U2FAuthenticate(
+ $('#js-authenticate-u2f'),
+ '#js-login-u2f-form',
+ gon.u2f,
+ document.querySelector('#js-login-2fa-device'),
+ document.querySelector('.js-2fa-form'),
+ );
+ u2fAuthenticate.start();
+ // needed in rspec
+ gl.u2fAuthenticate = u2fAuthenticate;
+};
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index d2f0d7410da..cd5ab53eace 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -13,12 +13,10 @@ Mousetrap.stopCallback = (e, element, combo) => {
};
export default class Shortcuts {
- constructor(skipResetBindings) {
+ constructor() {
this.onToggleHelp = this.onToggleHelp.bind(this);
this.enabledHelp = [];
- if (!skipResetBindings) {
- Mousetrap.reset();
- }
+
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', this.focusFilter.bind(this));
@@ -62,7 +60,7 @@ export default class Shortcuts {
e.preventDefault();
const performanceBarCookieName = 'perf_bar_enabled';
if (Cookies.get(performanceBarCookieName) === 'true') {
- Cookies.remove(performanceBarCookieName, { path: '/' });
+ Cookies.set(performanceBarCookieName, 'false', { path: '/' });
} else {
Cookies.set(performanceBarCookieName, 'true', { path: '/' });
}
diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js
index cf309be4f6f..908b9cab93d 100644
--- a/app/assets/javascripts/shortcuts_blob.js
+++ b/app/assets/javascripts/shortcuts_blob.js
@@ -1,4 +1,4 @@
-/* global Mousetrap */
+import Mousetrap from 'mousetrap';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
import Shortcuts from './shortcuts';
diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js
index 81286c0010c..1e246a56b85 100644
--- a/app/assets/javascripts/shortcuts_find_file.js
+++ b/app/assets/javascripts/shortcuts_find_file.js
@@ -1,5 +1,4 @@
-/* global Mousetrap */
-
+import Mousetrap from 'mousetrap';
import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsFindFile extends ShortcutsNavigation {
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 292e3d6a657..689befc742e 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -1,12 +1,10 @@
-/* global Mousetrap */
-
+import Mousetrap from 'mousetrap';
import _ from 'underscore';
-import 'mousetrap';
import Sidebar from './right_sidebar';
-import ShortcutsNavigation from './shortcuts_navigation';
+import Shortcuts from './shortcuts';
import { CopyAsGFM } from './behaviors/copy_as_gfm';
-export default class ShortcutsIssuable extends ShortcutsNavigation {
+export default class ShortcutsIssuable extends Shortcuts {
constructor(isMergeRequest) {
super();
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
index b4562701a3e..a4d10850471 100644
--- a/app/assets/javascripts/shortcuts_navigation.js
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -1,5 +1,4 @@
-/* global Mousetrap */
-
+import Mousetrap from 'mousetrap';
import findAndFollowLink from './shortcuts_dashboard_navigation';
import Shortcuts from './shortcuts';
diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/shortcuts_network.js
index 21823085ac4..a88c280fa3b 100644
--- a/app/assets/javascripts/shortcuts_network.js
+++ b/app/assets/javascripts/shortcuts_network.js
@@ -1,4 +1,4 @@
-/* global Mousetrap */
+import Mousetrap from 'mousetrap';
import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsNetwork extends ShortcutsNavigation {
diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js
index 59b967dbe09..41865dcf4ba 100644
--- a/app/assets/javascripts/shortcuts_wiki.js
+++ b/app/assets/javascripts/shortcuts_wiki.js
@@ -1,16 +1,14 @@
-/* eslint-disable class-methods-use-this */
-/* global Mousetrap */
-
+import Mousetrap from 'mousetrap';
import ShortcutsNavigation from './shortcuts_navigation';
import findAndFollowLink from './shortcuts_dashboard_navigation';
export default class ShortcutsWiki extends ShortcutsNavigation {
constructor() {
super();
- Mousetrap.bind('e', this.editWiki);
+ Mousetrap.bind('e', ShortcutsWiki.editWiki);
}
- editWiki() {
+ static editWiki() {
findAndFollowLink('.js-wiki-edit');
}
}
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
index 77f070d48cc..129ba2e4e89 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
@@ -39,7 +39,7 @@ export default {
class="js-sidebar-dropdown-toggle edit-link pull-right"
href="#"
>
- Edit
+ {{ __('Edit') }}
</a>
<a
v-if="showToggle"
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 839f9ec88b9..02153fb86a5 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -65,7 +65,7 @@
href="#"
@click.prevent="toggleForm"
>
- Edit
+ {{ __('Edit') }}
</a>
</div>
<div class="value sidebar-item-value hide-collapsed">
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index 7226076a8fc..d69d100a26c 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -1,12 +1,22 @@
<script>
- /* eslint-disable vue/require-default-prop */
- import { __ } from '../../../locale';
+ import { __ } from '~/locale';
+ import icon from '~/vue_shared/components/icon.vue';
+ import toggleButton from '~/vue_shared/components/toggle_button.vue';
+ import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
- import loadingButton from '../../../vue_shared/components/loading_button.vue';
+
+ const ICON_ON = 'notifications';
+ const ICON_OFF = 'notifications-off';
+ const LABEL_ON = __('Notifications on');
+ const LABEL_OFF = __('Notifications off');
export default {
+ directives: {
+ tooltip,
+ },
components: {
- loadingButton,
+ icon,
+ toggleButton,
},
props: {
loading: {
@@ -17,22 +27,23 @@
subscribed: {
type: Boolean,
required: false,
+ default: null,
},
id: {
type: Number,
required: false,
+ default: null,
},
},
computed: {
- buttonLabel() {
- let label;
- if (this.subscribed === false) {
- label = __('Subscribe');
- } else if (this.subscribed === true) {
- label = __('Unsubscribe');
- }
-
- return label;
+ showLoadingState() {
+ return this.subscribed === null;
+ },
+ notificationIcon() {
+ return this.subscribed ? ICON_ON : ICON_OFF;
+ },
+ notificationTooltip() {
+ return this.subscribed ? LABEL_ON : LABEL_OFF;
},
},
methods: {
@@ -46,21 +57,29 @@
<template>
<div>
<div class="sidebar-collapsed-icon">
- <i
- class="fa fa-rss"
- aria-hidden="true"
+ <span
+ v-tooltip
+ :title="notificationTooltip"
+ data-container="body"
+ data-placement="left"
>
- </i>
+ <icon
+ :name="notificationIcon"
+ :size="16"
+ aria-hidden="true"
+ class="sidebar-item-icon is-active"
+ />
+ </span>
</div>
<span class="issuable-header-text hide-collapsed pull-left">
{{ __('Notifications') }}
</span>
- <loading-button
- ref="loadingButton"
- class="btn btn-default pull-right hide-collapsed js-issuable-subscribe-button"
- :loading="loading"
- :label="buttonLabel"
- @click="toggleSubscription"
+ <toggle-button
+ ref="toggleButton"
+ class="pull-right hide-collapsed js-issuable-subscribe-button"
+ :is-loading="showLoadingState"
+ :value="subscribed"
+ @change="toggleSubscription"
/>
</div>
</template>
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index 8e167f5bf08..4cc1c96b870 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -32,8 +32,8 @@ export default class IssuableTemplateSelector extends TemplateSelector {
this.startLoadingSpinner();
Api.issueTemplate(this.namespacePath, this.projectPath, query.name, this.issuableType, (err, currentTemplate) => {
this.currentTemplate = currentTemplate;
- if (err) return; // Error handled by global AJAX error handler
this.stopLoadingSpinner();
+ if (err) return; // Error handled by global AJAX error handler
this.setInputValueToTemplateContent();
});
return;
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js
deleted file mode 100644
index b4e4a6aa161..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import statusIcon from '../mr_widget_status_icon';
-
-export default {
- name: 'MRWidgetArchived',
- components: {
- statusIcon,
- },
- template: `
- <div class="mr-widget-body media">
- <div class="space-children">
- <status-icon status="failed" />
- <button
- type="button"
- class="btn btn-success btn-sm"
- disabled="true">
- Merge
- </button>
- </div>
- <div class="media-body">
- <span class="bold">
- This project is archived, write access has been disabled
- </span>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
new file mode 100644
index 00000000000..afa9cc57544
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
@@ -0,0 +1,31 @@
+<script>
+ import statusIcon from '../mr_widget_status_icon';
+
+ export default {
+ name: 'MRWidgetArchived',
+ components: {
+ statusIcon,
+ },
+ };
+</script>
+<template>
+ <div class="mr-widget-body media">
+ <div class="space-children">
+ <status-icon
+ status="warning"
+ />
+ <button
+ type="button"
+ class="btn btn-success btn-sm"
+ disabled="true"
+ >
+ {{ s__("mrWidget|Merge") }}
+ </button>
+ </div>
+ <div class="media-body">
+ <span class="bold">
+ {{ s__("mrWidget|This project is archived, write access has been disabled") }}
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js
deleted file mode 100644
index 5648208f7b1..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import eventHub from '../../event_hub';
-import statusIcon from '../mr_widget_status_icon';
-
-export default {
- name: 'MRWidgetAutoMergeFailed',
- props: {
- mr: { type: Object, required: true },
- },
- data() {
- return {
- isRefreshing: false,
- };
- },
- components: {
- statusIcon,
- },
- methods: {
- refreshWidget() {
- this.isRefreshing = true;
- eventHub.$emit('MRWidgetUpdateRequested', () => {
- this.isRefreshing = false;
- });
- },
- },
- template: `
- <div class="mr-widget-body media">
- <status-icon status="failed" />
- <div class="media-body space-children">
- <span class="bold">
- <template v-if="mr.mergeError">{{mr.mergeError}}.</template>
- This merge request failed to be merged automatically
- </span>
- <button
- @click="refreshWidget"
- :disabled="isRefreshing"
- type="button"
- class="btn btn-xs btn-default">
- <i
- v-if="isRefreshing"
- class="fa fa-spinner fa-spin"
- aria-hidden="true" />
- Refresh
- </button>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
new file mode 100644
index 00000000000..77dd243d617
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -0,0 +1,52 @@
+<script>
+ import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+ import eventHub from '../../event_hub';
+ import statusIcon from '../mr_widget_status_icon';
+
+ export default {
+ name: 'MRWidgetAutoMergeFailed',
+ components: {
+ statusIcon,
+ loadingIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isRefreshing: false,
+ };
+ },
+ methods: {
+ refreshWidget() {
+ this.isRefreshing = true;
+ eventHub.$emit('MRWidgetUpdateRequested', () => {
+ this.isRefreshing = false;
+ });
+ },
+ },
+ };
+</script>
+<template>
+ <div class="mr-widget-body media">
+ <status-icon status="warning" />
+ <div class="media-body space-children">
+ <span class="bold">
+ <template v-if="mr.mergeError">{{ mr.mergeError }}.</template>
+ {{ s__("mrWidget|This merge request failed to be merged automatically") }}
+ </span>
+ <button
+ @click="refreshWidget"
+ :disabled="isRefreshing"
+ type="button"
+ class="btn btn-xs btn-default"
+ >
+ <loading-icon v-if="isRefreshing" />
+ {{ s__("mrWidget|Refresh") }}
+ </button>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
index dd8b2665b1d..dc19b20aa11 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
@@ -12,7 +12,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" />
+ <status-icon status="warning" />
<div class="media-body">
<mr-widget-author-and-time
actionText="Closed by"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
index 5d468a085cb..7a887bacfa7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
@@ -11,7 +11,7 @@ export default {
template: `
<div class="mr-widget-body media">
<status-icon
- status="failed"
+ status="warning"
:show-disabled-button="true" />
<div class="media-body space-children">
<span
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
index c25d6c359bb..fc5f18695b7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
@@ -51,7 +51,7 @@ export default {
</span>
</template>
<template v-else>
- <status-icon status="failed" :show-disabled-button="true" />
+ <status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
<span
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
index 1bc0b7e0819..16ff1109e3f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
@@ -24,7 +24,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" :show-disabled-button="true" />
+ <status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold js-branch-text">
<span class="capitalize">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
index 1cedf86e811..2c84f423ee2 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
@@ -7,7 +7,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" :show-disabled-button="true" />
+ <status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
Pipeline blocked. The pipeline for this merge request requires a manual action to proceed
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
index 6853ba4b9f8..cbaa73deffa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
@@ -7,7 +7,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" :show-disabled-button="true" />
+ <status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
index e82fb979162..e51eef07093 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
@@ -1,6 +1,7 @@
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
+import MergeRequest from '../../../merge_request';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon';
import eventHub from '../../event_hub';
@@ -68,7 +69,7 @@ export default {
},
iconClass() {
if (this.status === 'failed' || !this.commitMessage.length || !this.mr.isMergeAllowed || this.mr.preventMerge) {
- return 'failed';
+ return 'warning';
}
return 'success';
},
@@ -165,11 +166,9 @@ export default {
// If state is merged we should update the widget and stop the polling
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('FetchActionsContent');
- if (window.mergeRequest) {
- window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged');
- window.mergeRequest.hideCloseButton();
- window.mergeRequest.decreaseCounter();
- }
+ MergeRequest.setStatusBoxToMerged();
+ MergeRequest.hideCloseButton();
+ MergeRequest.decreaseCounter();
stopPolling();
// If user checked remove source branch and we didn't remove the branch yet
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
index af19cf6ab87..46687cc85e1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
@@ -7,7 +7,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" :show-disabled-button="true" />
+ <status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
The source branch HEAD has recently changed. Please reload the page and review the changes before merging
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
index a119ecbbdfe..97b1940f4be 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
@@ -10,7 +10,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" :show-disabled-button="true" />
+ <status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
There are unresolved discussions. Please resolve these discussions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
index 13461440ef2..b4b0f00445c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
@@ -37,7 +37,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" :show-disabled-button="Boolean(mr.removeWIPPath)" />
+ <status-icon status="warning" :show-disabled-button="Boolean(mr.removeWIPPath)" />
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 940f3d9b2d0..2dd3b2c2f98 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -21,7 +21,7 @@ export { default as FailedToMerge } from './components/states/mr_widget_failed_t
export { default as ClosedState } from './components/states/mr_widget_closed';
export { default as MergingState } from './components/states/mr_widget_merging';
export { default as WipState } from './components/states/mr_widget_wip';
-export { default as ArchivedState } from './components/states/mr_widget_archived';
+export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts';
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
@@ -33,7 +33,7 @@ export { default as PipelineBlockedState } from './components/states/mr_widget_p
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds';
export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
-export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed';
+export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue';
export { default as CheckingState } from './components/states/mr_widget_checking';
export { default as MRWidgetStore } from './stores/mr_widget_store';
export { default as MRWidgetService } from './services/mr_widget_service';
diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js
index 43ef468c303..6b9918b65b0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/index.js
@@ -2,6 +2,9 @@ import {
Vue,
mrWidgetOptions,
} from './dependencies';
+import Translate from '../vue_shared/translate';
+
+Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => {
gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 2075f8e4fec..98d33f9efaa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -1,4 +1,4 @@
-import Project from '~/project';
+import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import Flash from '../flash';
import {
diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue
index 1eba117b18f..12a75e016d7 100644
--- a/app/assets/javascripts/vue_shared/components/loading_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_icon.vue
@@ -33,7 +33,7 @@
<template>
<component
:is="rootElementType"
- class="text-center">
+ class="loading-container text-center">
<i
class="fa fa-spin fa-spinner"
:class="cssClass"
diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue
index c103c45c7dd..8227428d8ba 100644
--- a/app/assets/javascripts/vue_shared/components/modal.vue
+++ b/app/assets/javascripts/vue_shared/components/modal.vue
@@ -122,7 +122,7 @@
>
<button
type="button"
- class="btn pull-left"
+ class="btn"
:class="btnCancelKindClass"
@click="emitCancel($event)"
data-dismiss="modal"
@@ -132,7 +132,7 @@
<button
v-if="primaryButtonLabel"
type="button"
- class="btn pull-right js-primary-button"
+ class="btn js-primary-button"
:disabled="submitDisabled"
:class="btnKindClass"
@click="emitSubmit($event)"
diff --git a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
new file mode 100644
index 00000000000..86f06c8d266
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
@@ -0,0 +1,127 @@
+<script>
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+ directives: {
+ tooltip,
+ },
+ props: {
+ cssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ successLabel: {
+ type: String,
+ required: true,
+ },
+ failureLabel: {
+ type: String,
+ required: true,
+ },
+ neutralLabel: {
+ type: String,
+ required: true,
+ },
+ successCount: {
+ type: Number,
+ required: true,
+ },
+ failureCount: {
+ type: Number,
+ required: true,
+ },
+ totalCount: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ neutralCount() {
+ return this.totalCount - this.successCount - this.failureCount;
+ },
+ successPercent() {
+ return this.getPercent(this.successCount);
+ },
+ successBarStyle() {
+ return this.barStyle(this.successPercent);
+ },
+ successTooltip() {
+ return this.getTooltip(this.successLabel, this.successCount);
+ },
+ failurePercent() {
+ return this.getPercent(this.failureCount);
+ },
+ failureBarStyle() {
+ return this.barStyle(this.failurePercent);
+ },
+ failureTooltip() {
+ return this.getTooltip(this.failureLabel, this.failureCount);
+ },
+ neutralPercent() {
+ return this.getPercent(this.neutralCount);
+ },
+ neutralBarStyle() {
+ return this.barStyle(this.neutralPercent);
+ },
+ neutralTooltip() {
+ return this.getTooltip(this.neutralLabel, this.neutralCount);
+ },
+ },
+ methods: {
+ getPercent(count) {
+ return Math.ceil((count / this.totalCount) * 100);
+ },
+ barStyle(percent) {
+ return `width: ${percent}%;`;
+ },
+ getTooltip(label, count) {
+ return `${label}: ${count}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="stacked-progress-bar"
+ :class="cssClass"
+ >
+ <span
+ v-if="!totalCount"
+ class="status-unavailable"
+ >
+ {{ __("Not available") }}
+ </span>
+ <span
+ v-tooltip
+ v-if="successPercent"
+ class="status-green"
+ data-placement="bottom"
+ :title="successTooltip"
+ :style="successBarStyle"
+ >
+ {{ successPercent }}%
+ </span>
+ <span
+ v-tooltip
+ v-if="neutralPercent"
+ class="status-neutral"
+ data-placement="bottom"
+ :title="neutralTooltip"
+ :style="neutralBarStyle"
+ >
+ {{ neutralPercent }}%
+ </span>
+ <span
+ v-tooltip
+ v-if="failurePercent"
+ class="status-red"
+ data-placement="bottom"
+ :title="failureTooltip"
+ :style="failureBarStyle"
+ >
+ {{ failurePercent }}%
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue
index 2b12718ae96..09031d3ffa1 100644
--- a/app/assets/javascripts/vue_shared/components/toggle_button.vue
+++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue
@@ -23,11 +23,12 @@
name: {
type: String,
required: false,
- default: '',
+ default: null,
},
value: {
type: Boolean,
- required: true,
+ required: false,
+ default: null,
},
disabledInput: {
type: Boolean,
@@ -61,6 +62,7 @@
<template>
<label class="toggle-wrapper">
<input
+ v-if="name"
type="hidden"
:name="name"
:value="value"
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index 06a86f3b94a..4592003f57e 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -1,5 +1,4 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */
-/* global Mousetrap */
// Zen Mode (full screen) textarea
//
@@ -8,9 +7,11 @@
import 'vendor/jquery.scrollTo';
import Dropzone from 'dropzone';
-import 'mousetrap';
+import Mousetrap from 'mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause';
+Dropzone.autoDiscover = false;
+
//
// ### Events
//
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 43b16d3cf7d..cff47ea76ec 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -59,3 +59,4 @@
@import "framework/snippets";
@import "framework/memory_graph";
@import "framework/responsive_tables";
+@import "framework/stacked-progress-bar";
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index e0d2ed80de5..a538b5a2946 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -174,12 +174,13 @@
&.user-authored {
cursor: default;
- opacity: 0.65;
+ background-color: $gray-light;
+ border-color: $theme-gray-200;
+ color: $gl-text-color-disabled;
- &:hover,
- &:active {
- background-color: $white-light;
- border-color: $border-color;
+ gl-emoji {
+ opacity: 0.4;
+ filter: grayscale(100%);
}
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index fcc420923f9..d0b0c69b18f 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -220,14 +220,6 @@
@include btn-with-margin;
}
- &.disabled {
- pointer-events: auto !important;
- }
-
- &[disabled] {
- pointer-events: none !important;
- }
-
.fa-caret-down,
.fa-chevron-down {
margin-left: 5px;
@@ -450,3 +442,28 @@
.btn-svg svg {
@include btn-svg;
}
+
+// All disabled buttons, regardless of color, type, etc
+%disabled {
+ background-color: $gray-light !important;
+ border-color: $theme-gray-200 !important;
+ color: $gl-text-color-disabled !important;
+ opacity: 1 !important;
+ cursor: default !important;
+
+ i {
+ color: $gl-text-color-disabled !important;
+ }
+}
+
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn,
+.dropdown-toggle[disabled],
+[disabled].dropdown-menu-toggle {
+ @extend %disabled;
+
+ &:hover {
+ @extend %disabled;
+ }
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d1b3754d4ef..691df098c70 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -30,7 +30,7 @@
@include set-visible;
min-height: $dropdown-min-height;
max-height: $dropdown-max-height;
- overflow: auto;
+ overflow-y: auto;
@media (max-width: $screen-xs-max) {
width: 100%;
@@ -63,11 +63,6 @@
border-radius: $border-radius-base;
white-space: nowrap;
- &[disabled] {
- opacity: .65;
- cursor: not-allowed;
- }
-
&.no-outline {
outline: 0;
}
@@ -666,6 +661,16 @@
}
}
+.dropdown-create-new-item-button {
+ @include dropdown-link;
+
+ width: 100%;
+ background-color: transparent;
+ border: 0;
+ text-align: left;
+ text-overflow: ellipsis;
+}
+
.dropdown-loading {
position: absolute;
top: 0;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 1e91db5af9b..d835d49d8b2 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -124,6 +124,10 @@
&.wiki {
padding: $gl-padding;
+
+ @media (min-width: $screen-md-min) {
+ padding: $gl-padding * 2;
+ }
}
&.blob-no-preview {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index ad160f37641..634593aefd0 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -104,7 +104,10 @@
img {
height: 28px;
- margin-right: 8px;
+
+ + .logo-text {
+ margin-left: 8px;
+ }
}
&.wrap {
@@ -300,6 +303,8 @@
.projects-dropdown-menu {
padding: 0;
+ overflow-y: initial;
+ max-height: initial;
}
.dropdown-chevron {
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 1d8bd26cf1a..d8c57a0e2d9 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -24,15 +24,13 @@
font-size: $gl-font-size;
line-height: 25px;
+ &.status-box-closed,
&.status-box-mr-closed {
background-color: $gl-danger;
}
- &.status-box-issue-closed {
- background-color: $gl-primary;
- }
-
- &.status-box-merged {
+ &.status-box-issue-closed,
+ &.status-box-mr-merged {
background-color: $gl-primary;
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index fab3270b9f5..d107422e517 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -1,10 +1,16 @@
html {
overflow-y: scroll;
- &.touch .tooltip { display: none !important; }
+ &.touch .tooltip {
+ display: none !important;
+ }
}
body {
+ // Improves readability for dyslexic users; supported only in Chrome/Safari so far
+ // scss-lint:disable PropertySpelling
+ text-decoration-skip: ink;
+ // scss-lint:enable PropertySpelling
&.navless {
background-color: $white-light !important;
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index e12b5aab381..ddd9dbb2be4 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -17,6 +17,8 @@
*/
@mixin markdown-table {
width: auto;
+ display: block;
+ overflow-x: auto;
}
/*
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 1be66d0ab21..51ae09777fd 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -1,4 +1,5 @@
.modal-header {
+ background-color: $modal-body-bg;
padding: #{3 * $grid-size} #{2 * $grid-size};
.page-title {
@@ -8,8 +9,10 @@
.modal-body {
background-color: $modal-body-bg;
+ min-height: $modal-body-height;
position: relative;
padding: #{3 * $grid-size} #{2 * $grid-size};
+ text-align: left;
.form-actions {
margin: #{2 * $grid-size} #{-2 * $grid-size} #{-2 * $grid-size};
@@ -20,6 +23,30 @@
}
}
+.modal-footer {
+ display: flex;
+ flex-direction: row;
+
+ .btn + .btn {
+ margin-left: $grid-size;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ flex-direction: column;
+
+ .btn + .btn {
+ margin-left: 0;
+ margin-top: $grid-size;
+ }
+ }
+
+ @media (min-width: $screen-sm-min) {
+ .btn:first-of-type {
+ margin-left: auto;
+ }
+ }
+}
+
body.modal-open {
overflow: hidden;
}
@@ -32,12 +59,6 @@ body.modal-open {
}
}
-@media (min-width: $screen-md-min) {
- .modal-dialog {
- width: 860px;
- }
-}
-
@media (min-width: $screen-lg-min) {
.modal-full {
width: 98%;
diff --git a/app/assets/stylesheets/framework/stacked-progress-bar.scss b/app/assets/stylesheets/framework/stacked-progress-bar.scss
new file mode 100644
index 00000000000..4869cda73e5
--- /dev/null
+++ b/app/assets/stylesheets/framework/stacked-progress-bar.scss
@@ -0,0 +1,54 @@
+.stacked-progress-bar {
+ display: flex;
+ height: 16px;
+ border-radius: 10px;
+ overflow: hidden;
+ background-color: $theme-gray-100;
+
+ .status-unavailable,
+ .status-green,
+ .status-neutral,
+ .status-red, {
+ height: 100%;
+ min-width: 25px;
+ padding: 0 5px;
+ font-size: $tooltip-font-size;
+ font-weight: normal;
+ color: $white-light;
+ line-height: 16px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ .status-unavailable {
+ padding: 0 10px;
+ color: $theme-gray-700;
+ }
+
+ .status-green {
+ background-color: $green-500;
+
+ &:hover {
+ background-color: $green-600;
+ }
+ }
+
+ .status-neutral {
+ background-color: $theme-gray-200;
+ color: $gl-gray-dark;
+
+ &:hover {
+ background-color: $theme-gray-300;
+ }
+ }
+
+ .status-red {
+ background-color: $red-500;
+
+ &:hover {
+ background-color: $red-600;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index a23131e0818..d04e555769b 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -194,6 +194,6 @@ $modal-body-bg: $white-light;
//** Modal footer border color
// $modal-footer-border-color: $modal-header-border-color
-// $modal-lg: 900px
-// $modal-md: 600px
+$modal-lg: 860px;
+$modal-md: 540px;
// $modal-sm: 300px
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index ef1520f1f63..f76c6866463 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -164,6 +164,7 @@ $gl-text-color-tertiary: #949494;
$gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, .85);
+$gl-text-color-disabled: #919191;
$gl-text-green: $green-600;
$gl-text-green-hover: $green-700;
$gl-text-red: $red-500;
@@ -258,6 +259,8 @@ $general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px;
+$flash-height: 52px;
+$context-header-height: 60px;
/*
* Common component specific colors
@@ -733,3 +736,8 @@ $popup-box-shadow-color: rgba(90, 90, 90, 0.05);
Multi file editor
*/
$border-color-settings: #e1e1e1;
+
+/*
+Modals
+*/
+$modal-body-height: 134px;
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 3b35beb7695..cfef6476d4d 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -117,47 +117,6 @@
top: $gl-padding-top;
}
- .content-list {
- li {
- padding: 18px $gl-padding $gl-padding;
-
- .container-fluid {
- padding: 0;
- }
- }
-
- .title-col {
- p {
- margin: 0;
-
- &.title {
- line-height: 19px;
- font-size: 14px;
- font-weight: $gl-font-weight-bold;
- color: $gl-text-color;
- }
-
- &.text {
- color: $layout-link-gray;
-
- &.value-col {
- color: $gl-text-color;
- }
- }
- }
- }
-
- .value-col {
- text-align: right;
-
- span {
- position: relative;
- vertical-align: middle;
- top: 3px;
- }
- }
- }
-
.fa-spinner {
font-size: 28px;
position: relative;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index ae9a8b0182c..759719a72da 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -162,10 +162,6 @@
border: 0;
}
- span {
- display: inline-block;
- }
-
.select2-container span {
margin-top: 0;
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 6d4ccd53e12..bf8eb4c1f06 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -391,11 +391,17 @@
.dropdown-toggle {
float: right;
- .toggle-icon {
+ i {
color: $white-light;
padding-right: 2px;
margin-top: 2px;
}
+
+ &[disabled] {
+ i {
+ color: $gl-text-color-disabled;
+ }
+ }
}
.dropdown-menu {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index a35ebd48887..db88d4a16b7 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -69,13 +69,6 @@
border-color: $border-white-normal;
}
}
-
- .btn {
- .icon-play {
- height: 13px;
- width: 12px;
- }
- }
}
.btn .text-center {
@@ -235,7 +228,7 @@
.stage-cell {
&.table-section {
@media (min-width: $screen-md-min) {
- min-width: 148px;
+ min-width: 160px; /* Hack alert: Without this the mini graph pipeline won't work properly*/
margin-right: -4px;
}
}
@@ -798,7 +791,6 @@ button.mini-pipeline-graph-dropdown-toggle {
// link to the build
.mini-pipeline-graph-dropdown-item {
- padding: 3px 7px 4px;
align-items: center;
clear: both;
display: flex;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 61a76d0387a..bf41005b6d5 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -895,17 +895,6 @@ pre.light-well {
}
}
-.create-new-protected-branch-button,
-.create-new-protected-tag-button {
- @include dropdown-link;
-
- width: 100%;
- background-color: transparent;
- border: 0;
- text-align: left;
- text-overflow: ellipsis;
-}
-
.protected-branches-list,
.protected-tags-list {
margin-bottom: 30px;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index acbd9936706..8265b8370f7 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -107,6 +107,11 @@ table.table tr td.multi-file-table-name {
vertical-align: middle;
margin-right: 2px;
}
+
+ .loading-container {
+ margin-right: 4px;
+ display: inline-block;
+ }
}
.multi-file-table-col-commit-message {
@@ -247,7 +252,6 @@ table.table tr td.multi-file-table-name {
display: flex;
position: relative;
flex-direction: column;
- height: 100%;
width: 290px;
padding: 0;
background-color: $gray-light;
@@ -256,6 +260,11 @@ table.table tr td.multi-file-table-name {
.projects-sidebar {
display: flex;
flex-direction: column;
+
+ .context-header {
+ width: auto;
+ margin-right: 0;
+ }
}
.multi-file-commit-panel-inner {
@@ -496,19 +505,70 @@ table.table tr td.multi-file-table-name {
}
}
-.ide-flash-container.flash-container {
- margin-top: $header-height;
- margin-bottom: 0;
+.ide.nav-only {
+ .flash-container {
+ margin-top: $header-height;
+ margin-bottom: 0;
+ }
+
+ .alert-wrapper .flash-container .flash-alert:last-child,
+ .alert-wrapper .flash-container .flash-notice:last-child {
+ margin-bottom: 0;
+ }
+
+ .content {
+ margin-top: $header-height;
+ }
+
+ .multi-file-commit-panel .multi-file-commit-panel-inner-scroll {
+ max-height: calc(100vh - #{$header-height + $context-header-height});
+ }
+
+ &.flash-shown {
+ .content {
+ margin-top: 0;
+ }
+
+ .ide-view {
+ height: calc(100vh - #{$header-height + $flash-height});
+ }
+
+ .multi-file-commit-panel .multi-file-commit-panel-inner-scroll {
+ max-height: calc(100vh - #{$header-height + $flash-height + $context-header-height});
+ }
+ }
}
-.with-performance-bar {
- .ide-flash-container.flash-container {
- margin-top: $header-height + $performance-bar-height;
+.with-performance-bar .ide.nav-only {
+ .flash-container {
+ margin-top: #{$header-height + $performance-bar-height};
+ }
+
+ .content {
+ margin-top: #{$header-height + $performance-bar-height};
}
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height});
}
+
+ .multi-file-commit-panel .multi-file-commit-panel-inner-scroll {
+ max-height: calc(100vh - #{$header-height + $performance-bar-height + 60});
+ }
+
+ &.flash-shown {
+ .content {
+ margin-top: 0;
+ }
+
+ .ide-view {
+ height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height});
+ }
+
+ .multi-file-commit-panel .multi-file-commit-panel-inner-scroll {
+ max-height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height + $context-header-height});
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index c7297a34ad8..7d40c61da26 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -3,22 +3,21 @@
// see also: https://gist.github.com/jasonm23/2868981
$black: #000;
- $red: #cd0000;
- $green: #00cd00;
- $yellow: #cdcd00;
- $blue: #00e; // according to wikipedia, this is the xterm standard
- //$blue: #1e90ff; // this is used by all the terminals I tried (when configured with the xterm color profile)
- $magenta: #cd00cd;
- $cyan: #00cdcd;
- $white: #e5e5e5;
+ $red: #ea1010;
+ $green: #009900;
+ $yellow: #999900;
+ $blue: #0073e6;
+ $magenta: #d411d4;
+ $cyan: #009999;
+ $white: #ccc;
$l-black: #373b41;
- $l-red: #c66;
- $l-green: #b5bd68;
- $l-yellow: #f0c674;
- $l-blue: #81a2be;
- $l-magenta: #b294bb;
- $l-cyan: #8abeb7;
- $l-white: $gray-darkest;
+ $l-red: #ff6161;
+ $l-green: #00d600;
+ $l-yellow: #bdbd00;
+ $l-blue: #5797ff;
+ $l-magenta: #d96dd9;
+ $l-cyan: #00bdbd;
+ $l-white: #fff;
/*
* xterm colors
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index a7ab481519d..b0c4c31cffc 100644
--- a/app/controllers/admin/deploy_keys_controller.rb
+++ b/app/controllers/admin/deploy_keys_controller.rb
@@ -50,10 +50,10 @@ class Admin::DeployKeysController < Admin::ApplicationController
end
def create_params
- params.require(:deploy_key).permit(:key, :title, :can_push)
+ params.require(:deploy_key).permit(:key, :title)
end
def update_params
- params.require(:deploy_key).permit(:title, :can_push)
+ params.require(:deploy_key).permit(:title)
end
end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 77e3c95d197..2b47819303e 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -59,11 +59,9 @@ class Admin::HooksController < Admin::ApplicationController
def hook_params
params.require(:hook).permit(
:enable_ssl_verification,
- :push_events,
- :tag_push_events,
- :repository_update_events,
:token,
- :url
+ :url,
+ *SystemHook.triggers.values
)
end
end
diff --git a/app/controllers/admin/jobs_controller.rb b/app/controllers/admin/jobs_controller.rb
index 5162273ef8a..ae7a7f6279c 100644
--- a/app/controllers/admin/jobs_controller.rb
+++ b/app/controllers/admin/jobs_controller.rb
@@ -20,6 +20,6 @@ class Admin::JobsController < Admin::ApplicationController
def cancel_all
Ci::Build.running_or_pending.each(&:cancel)
- redirect_to admin_jobs_path
+ redirect_to admin_jobs_path, status: 303
end
end
diff --git a/app/controllers/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb
index b569029283f..fafb10090ca 100644
--- a/app/controllers/concerns/group_tree.rb
+++ b/app/controllers/concerns/group_tree.rb
@@ -2,7 +2,11 @@ module GroupTree
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_group_tree(groups)
@groups = if params[:filter].present?
- Gitlab::GroupHierarchy.new(groups.search(params[:filter]))
+ # We find the ancestors by ID of the search results here.
+ # Otherwise the ancestors would also have filters applied,
+ # which would cause them not to be preloaded.
+ group_ids = groups.search(params[:filter]).select(:id)
+ Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
.base_and_ancestors
else
# Only show root groups if no parent-id is given
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 74a4f437dc8..337957c366d 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -45,7 +45,7 @@ module IssuableActions
}
if issuable.edited?
- response[:updated_at] = issuable.updated_at
+ response[:updated_at] = issuable.last_edited_at.to_time.iso8601
response[:updated_by_name] = issuable.last_edited_by.name
response[:updated_by_path] = user_path(issuable.last_edited_by)
end
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
index 230bbe4b1aa..6a8b1a4de7b 100644
--- a/app/controllers/concerns/with_performance_bar.rb
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -6,13 +6,22 @@ module WithPerformanceBar
end
def peek_enabled?
- return true if Rails.env.development?
return false unless Gitlab::PerformanceBar.enabled?(current_user)
if RequestStore.active?
- RequestStore.fetch(:peek_enabled) { cookies[:perf_bar_enabled].present? }
+ RequestStore.fetch(:peek_enabled) { cookie_or_default_value }
else
- cookies[:perf_bar_enabled].present?
+ cookie_or_default_value
+ end
+ end
+
+ private
+
+ def cookie_or_default_value
+ if cookies[:perf_bar_enabled].present?
+ cookies[:perf_bar_enabled] == 'true'
+ else
+ cookies[:perf_bar_enabled] = 'true' if Rails.env.development?
end
end
end
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index bc0948cd3fb..6d9c38d9581 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -17,7 +17,7 @@ class ConfirmationsController < Devise::ConfirmationsController
else
Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}")
flash[:notice] += " Please sign in."
- new_session_path(:user)
+ new_session_path(:user, anchor: 'login-pane')
end
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index f013d21275e..acf6aaf57f4 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -75,8 +75,6 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def milestones
- search_params = params.merge(group_ids: group.id)
-
milestones = MilestonesFinder.new(search_params).execute
legacy_milestones = GroupMilestone.build_collection(group, group_projects, params)
@@ -94,4 +92,8 @@ class Groups::MilestonesController < Groups::ApplicationController
render_404 unless @milestone
end
+
+ def search_params
+ params.permit(:state).merge(group_ids: group.id)
+ end
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 689d2e3db22..d631d09f1b8 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -112,6 +112,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
continue_login_process
end
+ rescue Gitlab::OAuth::SigninDisabledForProviderError
+ handle_disabled_provider
rescue Gitlab::OAuth::SignupDisabledError
handle_signup_error
end
@@ -168,6 +170,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to new_user_session_path
end
+ def handle_disabled_provider
+ label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
+ flash[:alert] = "Signing in using #{label} has been disabled"
+
+ redirect_to new_user_session_path
+ end
+
def log_audit_event(user, options = {})
AuditEventService.new(user, user, options)
.for_authentication.security_event
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 2e7344b1cad..effb484ef0f 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :authorize_read_pipeline!, only: [:pipelines]
before_action :commit
- before_action :define_commit_vars, only: [:show, :diff_for_path, :pipelines]
+ before_action :define_commit_vars, only: [:show, :diff_for_path, :pipelines, :merge_requests]
before_action :define_note_vars, only: [:show, :diff_for_path]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
@@ -52,6 +52,18 @@ class Projects::CommitController < Projects::ApplicationController
end
end
+ def merge_requests
+ @merge_requests = @commit.merge_requests.map do |mr|
+ { iid: mr.iid, path: merge_request_path(mr), title: mr.title }
+ end
+
+ respond_to do |format|
+ format.json do
+ render json: @merge_requests.to_json
+ end
+ end
+ end
+
def branches
# branch_names_contains/tag_names_contains can take a long time when there are thousands of
# branches/tags - each `git branch --contains xxx` request can consume a cpu core.
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 026708169f4..0a40c67368f 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -13,31 +13,37 @@ class Projects::CommitsController < Projects::ApplicationController
@merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened
.find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
- respond_to do |format|
- format.html
- format.atom { render layout: 'xml.atom' }
-
- format.json do
- pager_json(
- 'projects/commits/_commits',
- @commits.size,
- project: @project,
- ref: @ref)
+ # https://gitlab.com/gitlab-org/gitaly/issues/931
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ respond_to do |format|
+ format.html
+ format.atom { render layout: 'xml.atom' }
+
+ format.json do
+ pager_json(
+ 'projects/commits/_commits',
+ @commits.size,
+ project: @project,
+ ref: @ref)
+ end
end
end
end
def signatures
- respond_to do |format|
- format.json do
- render json: {
- signatures: @commits.select(&:has_signature?).map do |commit|
- {
- commit_sha: commit.sha,
- html: view_to_html_string('projects/commit/_signature', signature: commit.signature)
- }
- end
- }
+ # https://gitlab.com/gitlab-org/gitaly/issues/931
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ respond_to do |format|
+ format.json do
+ render json: {
+ signatures: @commits.select(&:has_signature?).map do |commit|
+ {
+ commit_sha: commit.sha,
+ html: view_to_html_string('projects/commit/_signature', signature: commit.signature)
+ }
+ end
+ }
+ end
end
end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index e06dda1baa4..f43ef2e5f2f 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def create
@key = DeployKeys::CreateService.new(current_user, create_params).execute
- unless @key.valid? && @project.deploy_keys << @key
+ unless @key.valid?
flash[:alert] = @key.errors.full_messages.join(', ').html_safe
end
@@ -71,11 +71,14 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def create_params
- params.require(:deploy_key).permit(:key, :title, :can_push)
+ create_params = params.require(:deploy_key)
+ .permit(:key, :title, deploy_keys_projects_attributes: [:can_push])
+ create_params.dig(:deploy_keys_projects_attributes, '0')&.merge!(project_id: @project.id)
+ create_params
end
def update_params
- params.require(:deploy_key).permit(:title, :can_push)
+ params.require(:deploy_key).permit(:title, deploy_keys_projects_attributes: [:id, :can_push])
end
def authorize_update_deploy_key!
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 6f51e7b9b40..dd7aa1a67b9 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -64,18 +64,10 @@ class Projects::HooksController < Projects::ApplicationController
def hook_params
params.require(:hook).permit(
- :job_events,
- :pipeline_events,
:enable_ssl_verification,
- :issues_events,
- :confidential_issues_events,
- :merge_requests_events,
- :note_events,
- :push_events,
- :tag_push_events,
:token,
:url,
- :wiki_page_events
+ *ProjectHook.triggers.values
)
end
end
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 4865ec3dfe5..8b54ba3ad7c 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -29,7 +29,7 @@ class Projects::JobsController < Projects::ApplicationController
:project,
:tags
])
- @builds = @builds.page(params[:page]).per(30)
+ @builds = @builds.page(params[:page]).per(30).without_count
end
def cancel_all
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 3d2926d5d75..0df80fa700f 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -43,11 +43,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
end
def diffs
- @diffs = if @merge_request.can_be_created
- @merge_request.diffs(diff_options)
- else
- []
- end
+ @diffs = @merge_request.diffs(diff_options) if @merge_request.can_be_created
@diff_notes_disabled = true
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 980bbf699b6..0f70efbce40 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -92,12 +92,6 @@ class Projects::MilestonesController < Projects::ApplicationController
def milestones
@milestones ||= begin
- if @project.group && can?(current_user, :read_group, @project.group)
- group = @project.group
- end
-
- search_params = params.merge(project_ids: @project.id, group_ids: group&.id)
-
MilestonesFinder.new(search_params).execute
end
end
@@ -113,4 +107,12 @@ class Projects::MilestonesController < Projects::ApplicationController
def milestone_params
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end
+
+ def search_params
+ if @project.group && can?(current_user, :read_group, @project.group)
+ group = @project.group
+ end
+
+ params.permit(:state).merge(project_ids: @project.id, group_ids: group&.id)
+ end
end
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index 58570a580f1..e72fd8eb3a5 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -27,12 +27,16 @@ class GroupDescendantsFinder
end
def execute
- # The children array might be extended with the ancestors of projects when
- # filtering. In that case, take the maximum so the array does not get limited
- # Otherwise, allow paginating through all results
+ # The children array might be extended with the ancestors of projects and
+ # subgroups when filtering. In that case, take the maximum so the array does
+ # not get limited otherwise, allow paginating through all results.
#
all_required_elements = children
- all_required_elements |= ancestors_for_projects if params[:filter]
+ if params[:filter]
+ all_required_elements |= ancestors_of_filtered_subgroups
+ all_required_elements |= ancestors_of_filtered_projects
+ end
+
total_count = [all_required_elements.size, paginator.total_count].max
Kaminari.paginate_array(all_required_elements, total_count: total_count)
@@ -49,8 +53,11 @@ class GroupDescendantsFinder
end
def paginator
- @paginator ||= Gitlab::MultiCollectionPaginator.new(subgroups, projects,
- per_page: params[:per_page])
+ @paginator ||= Gitlab::MultiCollectionPaginator.new(
+ subgroups,
+ projects.with_route,
+ per_page: params[:per_page]
+ )
end
def direct_child_groups
@@ -94,15 +101,21 @@ class GroupDescendantsFinder
#
# So when searching 'project', on the 'subgroup' page we want to preload
# 'nested-group' but not 'subgroup' or 'root'
- def ancestors_for_groups(base_for_ancestors)
- Gitlab::GroupHierarchy.new(base_for_ancestors)
+ def ancestors_of_groups(base_for_ancestors)
+ group_ids = base_for_ancestors.except(:select, :sort).select(:id)
+ Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
.base_and_ancestors(upto: parent_group.id)
end
- def ancestors_for_projects
+ def ancestors_of_filtered_projects
projects_to_load_ancestors_of = projects.where.not(namespace: parent_group)
groups_to_load_ancestors_of = Group.where(id: projects_to_load_ancestors_of.select(:namespace_id))
- ancestors_for_groups(groups_to_load_ancestors_of)
+ ancestors_of_groups(groups_to_load_ancestors_of)
+ .with_selects_for_list(archived: params[:archived])
+ end
+
+ def ancestors_of_filtered_subgroups
+ ancestors_of_groups(subgroups)
.with_selects_for_list(archived: params[:archived])
end
@@ -112,7 +125,7 @@ class GroupDescendantsFinder
# When filtering subgroups, we want to find all matches withing the tree of
# descendants to show to the user
groups = if params[:filter]
- ancestors_for_groups(subgroups_matching_filter)
+ subgroups_matching_filter
else
direct_child_groups
end
@@ -121,8 +134,10 @@ class GroupDescendantsFinder
end
def direct_child_projects
- GroupProjectsFinder.new(group: parent_group, current_user: current_user, params: params)
- .execute
+ GroupProjectsFinder.new(group: parent_group,
+ current_user: current_user,
+ options: { only_owned: true },
+ params: params).execute
end
# Finds all projects nested under `parent_group` or any of its descendant
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 6de9eb89468..1427cdaa382 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -71,7 +71,7 @@ class LabelsFinder < UnionFinder
end
def projects?
- params[:project_ids].present?
+ params[:project_ids]
end
def only_group_labels?
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index 0a5a0ea2f35..b4605fca193 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -46,11 +46,7 @@ class MilestonesFinder
end
def order(items)
- if params.has_key?(:order)
- items.reorder(params[:order])
- else
- order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
- items.reorder(order_statement)
- end
+ order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
+ items.reorder(order_statement).order('title ASC')
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 45f7d29eb05..8ef561d90e6 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -201,6 +201,7 @@ module ApplicationSettingsHelper
:metrics_sample_interval,
:metrics_timeout,
:password_authentication_enabled_for_web,
+ :password_authentication_enabled_for_git,
:performance_bar_allowed_group_id,
:performance_bar_enabled,
:plantuml_enabled,
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 5e3b2e5581c..a6e1de6ffdc 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,6 +1,8 @@
module BlobHelper
def highlight(blob_name, blob_content, repository: nil, plain: false)
+ plain ||= blob_content.length > Blob::MAXIMUM_TEXT_HIGHLIGHT_SIZE
highlighted = Gitlab::Highlight.highlight(blob_name, blob_content, plain: plain, repository: repository)
+
raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>)
end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 878bc9b5c9c..4ddc1dbed49 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -80,4 +80,20 @@ module EmailsHelper
'text-align:center'
].join(';')
end
+
+ # "You are receiving this email because #{reason}"
+ def notification_reason_text(reason)
+ string = case reason
+ when NotificationReason::OWN_ACTIVITY
+ 'of your activity'
+ when NotificationReason::ASSIGNED
+ 'you have been assigned an item'
+ when NotificationReason::MENTIONED
+ 'you have been mentioned'
+ else
+ 'of your account'
+ end
+
+ "#{string} on #{Gitlab.config.gitlab.host}"
+ end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 2668cf78afe..7cd84fe69c9 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -241,7 +241,7 @@ module IssuablesHelper
return {} unless issuable.edited?
{
- updatedAt: issuable.updated_at.to_time.iso8601,
+ updatedAt: issuable.last_edited_at.to_time.iso8601,
updatedBy: {
name: issuable.last_edited_by.name,
path: user_path(issuable.last_edited_by)
@@ -304,6 +304,12 @@ module IssuablesHelper
issuable.model_name.human.downcase
end
+ def selected_labels
+ Array(params[:label_name]).map do |label_name|
+ Label.new(title: label_name)
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 0f110bd25c5..64cd3032780 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -72,7 +72,7 @@ module IssuesHelper
if item.try(:expired?)
'status-box-expired'
elsif item.try(:merged?)
- 'status-box-merged'
+ 'status-box-mr-merged'
elsif item.closed?
'status-box-mr-closed'
elsif item.try(:upcoming?)
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index e7c953e749e..ddb48371c79 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -54,8 +54,16 @@ module TodosHelper
def todo_target_state_pill(todo)
return unless show_todo_state?(todo)
+ type =
+ case todo.target
+ when MergeRequest
+ 'mr'
+ when Issue
+ 'issue'
+ end
+
content_tag(:span, nil, class: 'target-status') do
- content_tag(:span, nil, class: "status-box status-box-#{todo.target.state.dasherize}") do
+ content_tag(:span, nil, class: "status-box status-box-#{type}-#{todo.target.state.dasherize}") do
todo.target.state.capitalize
end
end
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 33453dd178f..94887c2cbd2 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -1,12 +1,12 @@
require 'webpack/rails/manifest'
module WebpackHelper
- def webpack_bundle_tag(bundle)
- javascript_include_tag(*gitlab_webpack_asset_paths(bundle))
+ def webpack_bundle_tag(bundle, force_same_domain: false)
+ javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: true))
end
# override webpack-rails gem helper until changes can make it upstream
- def gitlab_webpack_asset_paths(source, extension: nil)
+ def gitlab_webpack_asset_paths(source, extension: nil, force_same_domain: false)
return "" unless source.present?
paths = Webpack::Rails::Manifest.asset_paths(source)
@@ -14,9 +14,11 @@ module WebpackHelper
paths.select! { |p| p.ends_with? ".#{extension}" }
end
- force_host = webpack_public_host
- if force_host
- paths.map! { |p| "#{force_host}#{p}" }
+ unless force_same_domain
+ force_host = webpack_public_host
+ if force_host
+ paths.map! { |p| "#{force_host}#{p}" }
+ end
end
paths
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 64ca2d2eacf..b33131becd3 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -1,54 +1,54 @@
module Emails
module Issues
- def new_issue_email(recipient_id, issue_id)
+ def new_issue_email(recipient_id, issue_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
- mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id))
+ mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
end
- def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id)
+ def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
- mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
- def reassigned_issue_email(recipient_id, issue_id, previous_assignee_ids, updated_by_user_id)
+ def reassigned_issue_email(recipient_id, issue_id, previous_assignee_ids, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
@previous_assignees = []
@previous_assignees = User.where(id: previous_assignee_ids) if previous_assignee_ids.any?
- mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
- def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
+ def closed_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
@updated_by = User.find(updated_by_user_id)
- mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
- def relabeled_issue_email(recipient_id, issue_id, label_names, updated_by_user_id)
+ def relabeled_issue_email(recipient_id, issue_id, label_names, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
@label_names = label_names
@labels_url = project_labels_url(@project)
- mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
- def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
+ def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
@issue_status = status
@updated_by = User.find(updated_by_user_id)
- mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
- def issue_moved_email(recipient, issue, new_issue, updated_by_user)
+ def issue_moved_email(recipient, issue, new_issue, updated_by_user, reason = nil)
setup_issue_mail(issue.id, recipient.id)
@new_issue = new_issue
@new_project = new_issue.project
- mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id))
+ mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
end
private
@@ -61,11 +61,12 @@ module Emails
@sent_notification = SentNotification.record(@issue, recipient_id, reply_key)
end
- def issue_thread_options(sender_id, recipient_id)
+ def issue_thread_options(sender_id, recipient_id, reason)
{
from: sender(sender_id),
to: recipient(recipient_id),
- subject: subject("#{@issue.title} (##{@issue.iid})")
+ subject: subject("#{@issue.title} (##{@issue.iid})"),
+ 'X-GitLab-NotificationReason' => reason
}
end
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 3626f8ce416..5fe09cea83f 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -1,57 +1,57 @@
module Emails
module MergeRequests
- def new_merge_request_email(recipient_id, merge_request_id)
+ def new_merge_request_email(recipient_id, merge_request_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
- mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id))
+ mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id, reason))
end
- def new_mention_in_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
+ def new_mention_in_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
- mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
- def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
+ def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
- mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
- def relabeled_merge_request_email(recipient_id, merge_request_id, label_names, updated_by_user_id)
+ def relabeled_merge_request_email(recipient_id, merge_request_id, label_names, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@label_names = label_names
@labels_url = project_labels_url(@project)
- mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
- def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
+ def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@updated_by = User.find(updated_by_user_id)
- mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
- def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
+ def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
- mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
- def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id)
+ def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@mr_status = status
@updated_by = User.find(updated_by_user_id)
- mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
- def resolved_all_discussions_email(recipient_id, merge_request_id, resolved_by_user_id)
+ def resolved_all_discussions_email(recipient_id, merge_request_id, resolved_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@resolved_by = User.find(resolved_by_user_id)
- mail_answer_thread(@merge_request, merge_request_thread_options(resolved_by_user_id, recipient_id))
+ mail_answer_thread(@merge_request, merge_request_thread_options(resolved_by_user_id, recipient_id, reason))
end
private
@@ -64,11 +64,12 @@ module Emails
@sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key)
end
- def merge_request_thread_options(sender_id, recipient_id)
+ def merge_request_thread_options(sender_id, recipient_id, reason = nil)
{
from: sender(sender_id),
to: recipient(recipient_id),
- subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})")
+ subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})"),
+ 'X-GitLab-NotificationReason' => reason
}
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index ec886e993c3..eade0fe278f 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -112,6 +112,8 @@ class Notify < BaseMailer
headers["X-GitLab-#{model.class.name}-ID"] = model.id
headers['X-GitLab-Reply-Key'] = reply_key
+ @reason = headers['X-GitLab-NotificationReason']
+
if Gitlab::IncomingEmail.enabled? && @sent_notification
address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 6012dbba1b9..df67fb243ad 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -8,6 +8,7 @@ module Ci
MissingDependenciesError = Class.new(StandardError)
+ belongs_to :project, inverse_of: :builds
belongs_to :runner
belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User'
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index d4690da3be6..d7153d7b816 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -7,7 +7,7 @@ module Ci
include Presentable
include Gitlab::OptimisticLocking
- belongs_to :project
+ belongs_to :project, inverse_of: :pipelines
belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 39d7f5b159d..2d2d89af030 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -238,6 +238,10 @@ class Commit
notes.includes(:author)
end
+ def merge_requests
+ @merge_requests ||= project.merge_requests.by_commit_sha(sha)
+ end
+
def method_missing(method, *args, &block)
@raw.__send__(method, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
@@ -342,10 +346,11 @@ class Commit
@merged_merge_request_hash[current_user]
end
- def has_been_reverted?(current_user, noteable = self)
+ def has_been_reverted?(current_user, notes_association = nil)
ext = all_references(current_user)
+ notes_association ||= notes_with_associations
- noteable.notes_with_associations.system.each do |note|
+ notes_association.system.each do |note|
note.all_references(current_user, extractor: ext)
end
@@ -367,19 +372,19 @@ class Commit
# uri_type('doc/README.md') # => :blob
# uri_type('doc/logo.png') # => :raw
# uri_type('doc/api') # => :tree
- # uri_type('not/found') # => :nil
+ # uri_type('not/found') # => nil
#
# Returns a symbol
def uri_type(path)
- entry = @raw.rugged_tree_entry(path)
+ entry = @raw.tree_entry(path)
+ return unless entry
+
if entry[:type] == :blob
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project)
blob.image? || blob.video? ? :raw : :blob
else
entry[:type]
end
- rescue Rugged::TreeError
- nil
end
def raw_diffs(*args)
diff --git a/app/models/concerns/resolvable_discussion.rb b/app/models/concerns/resolvable_discussion.rb
index b6c7b6735b9..7c236369793 100644
--- a/app/models/concerns/resolvable_discussion.rb
+++ b/app/models/concerns/resolvable_discussion.rb
@@ -1,5 +1,6 @@
module ResolvableDiscussion
extend ActiveSupport::Concern
+ include ::Gitlab::Utils::StrongMemoize
included do
# A number of properties of this `Discussion`, like `first_note` and `resolvable?`, are memoized.
@@ -31,27 +32,37 @@ module ResolvableDiscussion
end
def resolvable?
- @resolvable ||= potentially_resolvable? && notes.any?(&:resolvable?)
+ strong_memoize(:resolvable) do
+ potentially_resolvable? && notes.any?(&:resolvable?)
+ end
end
def resolved?
- @resolved ||= resolvable? && notes.none?(&:to_be_resolved?)
+ strong_memoize(:resolved) do
+ resolvable? && notes.none?(&:to_be_resolved?)
+ end
end
def first_note
- @first_note ||= notes.first
+ strong_memoize(:first_note) do
+ notes.first
+ end
end
def first_note_to_resolve
return unless resolvable?
- @first_note_to_resolve ||= notes.find(&:to_be_resolved?) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ strong_memoize(:first_note_to_resolve) do
+ notes.find(&:to_be_resolved?)
+ end
end
def last_resolved_note
return unless resolved?
- @last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ strong_memoize(:last_resolved_note) do
+ resolved_notes.sort_by(&:resolved_at).last
+ end
end
def resolved_notes
@@ -93,8 +104,8 @@ module ResolvableDiscussion
# Set the notes array to the updated notes
@notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables
- self.class.memoized_values.each do |var|
- instance_variable_set(:"@#{var}", nil)
+ self.class.memoized_values.each do |name|
+ clear_memoization(name)
end
end
end
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index 67ecf470f7e..703a72c355c 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -3,6 +3,7 @@ module ShaAttribute
module ClassMethods
def sha_attribute(name)
+ return if ENV['STATIC_VERIFICATION']
return unless table_exists?
column = columns.find { |c| c.name == name.to_s }
diff --git a/app/models/concerns/triggerable_hooks.rb b/app/models/concerns/triggerable_hooks.rb
new file mode 100644
index 00000000000..ec0ed3b795a
--- /dev/null
+++ b/app/models/concerns/triggerable_hooks.rb
@@ -0,0 +1,40 @@
+module TriggerableHooks
+ AVAILABLE_TRIGGERS = {
+ repository_update_hooks: :repository_update_events,
+ push_hooks: :push_events,
+ tag_push_hooks: :tag_push_events,
+ issue_hooks: :issues_events,
+ confidential_issue_hooks: :confidential_issues_events,
+ note_hooks: :note_events,
+ merge_request_hooks: :merge_requests_events,
+ job_hooks: :job_events,
+ pipeline_hooks: :pipeline_events,
+ wiki_page_hooks: :wiki_page_events
+ }.freeze
+
+ extend ActiveSupport::Concern
+
+ class_methods do
+ attr_reader :triggerable_hooks
+
+ attr_reader :triggers
+
+ def hooks_for(trigger)
+ callable_scopes = triggers.keys + [:all]
+ return none unless callable_scopes.include?(trigger)
+
+ public_send(trigger) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ private
+
+ def triggerable_hooks(hooks)
+ triggers = AVAILABLE_TRIGGERS.slice(*hooks)
+ @triggers = triggers
+
+ triggers.each do |trigger, event|
+ scope trigger, -> { where(event => true) }
+ end
+ end
+ end
+end
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index eae5eee4fee..c2e0a5fa126 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -1,10 +1,16 @@
class DeployKey < Key
- has_many :deploy_keys_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ include IgnorableColumn
+
+ has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :deploy_keys_projects
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
scope :are_public, -> { where(public: true) }
+ ignore_column :can_push
+
+ accepts_nested_attributes_for :deploy_keys_projects
+
def private?
!public?
end
@@ -22,10 +28,18 @@ class DeployKey < Key
end
def has_access_to?(project)
- projects.include?(project)
+ deploy_keys_project_for(project).present?
end
def can_push_to?(project)
- can_push? && has_access_to?(project)
+ !!deploy_keys_project_for(project)&.can_push?
+ end
+
+ def deploy_keys_project_for(project)
+ deploy_keys_projects.find_by(project: project)
+ end
+
+ def projects_with_write_access
+ Project.preload(:route).where(id: deploy_keys_projects.with_write_access.select(:project_id))
end
end
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index b37b9bfbdac..6eef12c4373 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -1,8 +1,14 @@
class DeployKeysProject < ActiveRecord::Base
belongs_to :project
- belongs_to :deploy_key
+ belongs_to :deploy_key, inverse_of: :deploy_keys_projects
- validates :deploy_key_id, presence: true
+ scope :without_project_deleted, -> { joins(:project).where(projects: { pending_delete: false }) }
+ scope :in_project, ->(project) { where(project: project) }
+ scope :with_write_access, -> { where(can_push: true) }
+
+ accepts_nested_attributes_for :deploy_key
+
+ validates :deploy_key, presence: true
validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_id, presence: true
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index c0864769314..dc2f6817190 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -44,10 +44,10 @@ class GlobalMilestone
def self.group_milestones_states_count(group)
return STATE_COUNT_HASH unless group
- params = { group_ids: [group.id], state: 'all', order: nil }
+ params = { group_ids: [group.id], state: 'all' }
relation = MilestonesFinder.new(params).execute
- grouped_by_state = relation.group(:state).count
+ grouped_by_state = relation.reorder(nil).group(:state).count
{
opened: grouped_by_state['active'] || 0,
@@ -60,10 +60,10 @@ class GlobalMilestone
def self.legacy_group_milestone_states_count(projects)
return STATE_COUNT_HASH unless projects
- params = { project_ids: projects.map(&:id), state: 'all', order: nil }
+ params = { project_ids: projects.map(&:id), state: 'all' }
relation = MilestonesFinder.new(params).execute
- project_milestones_by_state_and_title = relation.group(:state, :title).count
+ project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count
opened = count_by_state(project_milestones_by_state_and_title, 'active')
closed = count_by_state(project_milestones_by_state_and_title, 'closed')
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index a8c424a6614..b6dd39b860b 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -1,19 +1,17 @@
class ProjectHook < WebHook
- TRIGGERS = {
- push_hooks: :push_events,
- tag_push_hooks: :tag_push_events,
- issue_hooks: :issues_events,
- confidential_issue_hooks: :confidential_issues_events,
- note_hooks: :note_events,
- merge_request_hooks: :merge_requests_events,
- job_hooks: :job_events,
- pipeline_hooks: :pipeline_events,
- wiki_page_hooks: :wiki_page_events
- }.freeze
+ include TriggerableHooks
- TRIGGERS.each do |trigger, event|
- scope trigger, -> { where(event => true) }
- end
+ triggerable_hooks [
+ :push_hooks,
+ :tag_push_hooks,
+ :issue_hooks,
+ :confidential_issue_hooks,
+ :note_hooks,
+ :merge_request_hooks,
+ :job_hooks,
+ :pipeline_hooks,
+ :wiki_page_hooks
+ ]
belongs_to :project
validates :project, presence: true
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 180c479c41b..0528266e5b3 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -1,14 +1,14 @@
class SystemHook < WebHook
- TRIGGERS = {
- repository_update_hooks: :repository_update_events,
- push_hooks: :push_events,
- tag_push_hooks: :tag_push_events
- }.freeze
+ include TriggerableHooks
- TRIGGERS.each do |trigger, event|
- scope trigger, -> { where(event => true) }
- end
+ triggerable_hooks [
+ :repository_update_hooks,
+ :push_hooks,
+ :tag_push_hooks,
+ :merge_request_hooks
+ ]
default_value_for :push_events, false
default_value_for :repository_update_events, true
+ default_value_for :merge_requests_events, false
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 5a70e114f56..27729deeac9 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :url, presence: true, url: true
+ validates :token, format: { without: /\n/ }
def execute(data, hook_name)
WebHookService.new(self, data, hook_name).execute
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2669d2a6ff3..8028ff3875b 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -140,7 +140,9 @@ class MergeRequest < ActiveRecord::Base
scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :from_source_branches, ->(branches) { where(source_branch: branches) }
-
+ scope :by_commit_sha, ->(sha) do
+ where('EXISTS (?)', MergeRequestDiff.select(1).where('merge_requests.latest_merge_request_diff_id = merge_request_diffs.id').by_commit_sha(sha)).reorder(nil)
+ end
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
scope :assigned, -> { where("assignee_id IS NOT NULL") }
@@ -982,7 +984,16 @@ class MergeRequest < ActiveRecord::Base
end
def can_be_reverted?(current_user)
- merge_commit && !merge_commit.has_been_reverted?(current_user, self)
+ return false unless merge_commit
+
+ merged_at = metrics&.merged_at
+ notes_association = notes_with_associations
+
+ if merged_at
+ notes_association = notes_association.where('created_at > ?', merged_at)
+ end
+
+ !merge_commit.has_been_reverted?(current_user, notes_association)
end
def can_be_cherry_picked?
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index afab72930c1..69a846da9be 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -28,6 +28,9 @@ class MergeRequestDiff < ActiveRecord::Base
end
scope :viewable, -> { without_state(:empty) }
+ scope :by_commit_sha, ->(sha) do
+ joins(:merge_request_diff_commits).where(merge_request_diff_commits: { sha: sha }).reorder(nil)
+ end
scope :recent, -> { order(id: :desc).limit(100) }
diff --git a/app/models/notification_reason.rb b/app/models/notification_reason.rb
new file mode 100644
index 00000000000..c3965565022
--- /dev/null
+++ b/app/models/notification_reason.rb
@@ -0,0 +1,19 @@
+# Holds reasons for a notification to have been sent as well as a priority list to select which reason to use
+# above the rest
+class NotificationReason
+ OWN_ACTIVITY = 'own_activity'.freeze
+ ASSIGNED = 'assigned'.freeze
+ MENTIONED = 'mentioned'.freeze
+
+ # Priority list for selecting which reason to return in the notification
+ REASON_PRIORITY = [
+ OWN_ACTIVITY,
+ ASSIGNED,
+ MENTIONED
+ ].freeze
+
+ # returns the priority of a reason as an integer
+ def self.priority(reason)
+ REASON_PRIORITY.index(reason) || REASON_PRIORITY.length + 1
+ end
+end
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index ab5a96209c7..472b348a545 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -1,27 +1,19 @@
class NotificationRecipient
- attr_reader :user, :type
- def initialize(
- user, type,
- custom_action: nil,
- target: nil,
- acting_user: nil,
- project: nil,
- group: nil,
- skip_read_ability: false
- )
-
+ attr_reader :user, :type, :reason
+ def initialize(user, type, **opts)
unless NotificationSetting.levels.key?(type) || type == :subscription
raise ArgumentError, "invalid type: #{type.inspect}"
end
- @custom_action = custom_action
- @acting_user = acting_user
- @target = target
- @project = project || default_project
- @group = group || @project&.group
+ @custom_action = opts[:custom_action]
+ @acting_user = opts[:acting_user]
+ @target = opts[:target]
+ @project = opts[:project] || default_project
+ @group = opts[:group] || @project&.group
@user = user
@type = type
- @skip_read_ability = skip_read_ability
+ @reason = opts[:reason]
+ @skip_read_ability = opts[:skip_read_ability]
end
def notification_setting
@@ -77,9 +69,15 @@ class NotificationRecipient
def own_activity?
return false unless @acting_user
- return false if @acting_user.notified_of_own_activity?
- user == @acting_user
+ if user == @acting_user
+ # if activity was generated by the same user, change reason to :own_activity
+ @reason = NotificationReason::OWN_ACTIVITY
+ # If the user wants to be notified, we must return `false`
+ !@acting_user.notified_of_own_activity?
+ else
+ false
+ end
end
def has_access?
diff --git a/app/models/project.rb b/app/models/project.rb
index 029f2da2e4e..c0f7b30ceb0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -20,6 +20,7 @@ class Project < ActiveRecord::Base
include GroupDescendant
include Gitlab::SQL::Pattern
include DeploymentPlatform
+ include ::Gitlab::Utils::StrongMemoize
extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings
@@ -198,13 +199,13 @@ class Project < ActiveRecord::Base
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :commit_statuses
- has_many :pipelines, class_name: 'Ci::Pipeline'
+ has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
# Ci::Build objects store data on the file system such as artifact files and
# build traces. Currently there's no efficient way of removing this data in
# bulk that doesn't involve loading the rows into memory. As a result we're
# still using `dependent: :destroy` here.
- has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName'
has_many :runner_projects, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
@@ -313,6 +314,7 @@ class Project < ActiveRecord::Base
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
+ scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) }
scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) }
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
@@ -966,10 +968,12 @@ class Project < ActiveRecord::Base
def execute_hooks(data, hooks_scope = :push_hooks)
run_after_commit_or_now do
- hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
+ hooks.hooks_for(hooks_scope).each do |hook|
hook.async_execute(data, hooks_scope.to_s)
end
end
+
+ SystemHooksService.new.execute_hooks(data, hooks_scope)
end
def execute_services(data, hooks_scope = :push_hooks)
@@ -993,9 +997,13 @@ class Project < ActiveRecord::Base
end
def repo_exists?
- @repo_exists ||= repository.exists?
- rescue
- @repo_exists = false
+ strong_memoize(:repo_exists) do
+ begin
+ repository.exists?
+ rescue
+ false
+ end
+ end
end
def root_ref?(branch)
@@ -1430,7 +1438,7 @@ class Project < ActiveRecord::Base
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
# the import rake task.
- repository.rugged.config['gitlab.fullpath'] = gl_full_path
+ repository.raw_repository.write_config(full_path: gl_full_path)
rescue Gitlab::Git::Repository::NoRepository => e
Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
nil
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 17b9d2cf7b4..87a4350f022 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -37,7 +37,7 @@ class ProjectStatistics < ActiveRecord::Base
def update_build_artifacts_size
self.build_artifacts_size =
project.builds.sum(:artifacts_size) +
- Ci::JobArtifact.artifacts_size_for(self)
+ Ci::JobArtifact.artifacts_size_for(self.project)
end
def update_storage_size
diff --git a/app/models/push_event.rb b/app/models/push_event.rb
index 83ce9014094..90c085c888e 100644
--- a/app/models/push_event.rb
+++ b/app/models/push_event.rb
@@ -46,10 +46,11 @@ class PushEvent < Event
# Returns PushEvent instances for which no merge requests have been created.
def self.without_existing_merge_requests
- existing_mrs = MergeRequest.except(:order)
+ existing_mrs = MergeRequest.except(:order, :where)
.select(1)
.where('merge_requests.source_project_id = events.project_id')
.where('merge_requests.source_branch = push_event_payloads.ref')
+ .where(state: :opened)
# For reasons unknown the use of #eager_load will result in the
# "push_event_payload" association not being set. Because of this we're
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d27212b2058..73c4899cb9b 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -259,15 +259,7 @@ class Repository
return if kept_around?(sha)
# This will still fail if the file is corrupted (e.g. 0 bytes)
- begin
- raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
- rescue Rugged::ReferenceError => ex
- Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
- rescue Rugged::OSError => ex
- raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
-
- Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
- end
+ raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
end
def kept_around?(sha)
@@ -831,13 +823,12 @@ class Repository
end
def can_be_merged?(source_sha, target_branch)
- our_commit = rugged.branches[target_branch].target
- their_commit = rugged.lookup(source_sha)
-
- if our_commit && their_commit
- !rugged.merge_commits(our_commit, their_commit).conflicts?
- else
- false
+ raw_repository.gitaly_migrate(:can_be_merged) do |is_enabled|
+ if is_enabled
+ gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
+ else
+ rugged_can_be_merged?(source_sha, target_branch)
+ end
end
end
@@ -895,15 +886,18 @@ class Repository
branch = Gitlab::Git::Branch.find(self, branch_or_name)
if branch
- @root_ref_sha ||= commit(root_ref).sha
- same_head = branch.target == @root_ref_sha
- merged = ancestor?(branch.target, @root_ref_sha)
+ same_head = branch.target == root_ref_sha
+ merged = ancestor?(branch.target, root_ref_sha)
!same_head && merged
else
nil
end
end
+ def root_ref_sha
+ @root_ref_sha ||= commit(root_ref).sha
+ end
+
delegate :merged_branch_names, to: :raw_repository
def merge_base(first_commit_id, second_commit_id)
@@ -930,15 +924,17 @@ class Repository
return [] if empty? || query.blank?
offset = 2
- args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
+ args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
run_git(args).first.scrub.split(/^--$/)
end
def search_files_by_name(query, ref)
- return [] if empty? || query.blank?
+ safe_query = Regexp.escape(query.sub(/^\/*/, ""))
+
+ return [] if empty? || safe_query.blank?
- args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
+ args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query})
run_git(args).first.lines.map(&:strip)
end
@@ -1129,6 +1125,14 @@ class Repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki))
end
+ def gitaly_can_be_merged?(their_commit, our_commit)
+ !raw_repository.gitaly_conflicts_client(our_commit, their_commit).conflicts?
+ end
+
+ def rugged_can_be_merged?(their_commit, our_commit)
+ !rugged.merge_commits(our_commit, their_commit).conflicts?
+ end
+
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref
diff --git a/app/models/route.rb b/app/models/route.rb
index 7ba3ec06041..3d4b5a8b5ee 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -1,4 +1,6 @@
class Route < ActiveRecord::Base
+ include CaseSensitivity
+
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
validates :source, presence: true
@@ -8,8 +10,9 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
- validate :ensure_permanent_paths
+ validate :ensure_permanent_paths, if: :path_changed?
+ before_validation :delete_conflicting_orphaned_routes
after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed?
after_update :create_redirect_for_old_path
@@ -78,4 +81,13 @@ class Route < ActiveRecord::Base
def conflicting_redirect_exists?
RedirectRoute.permanent.matching_path_and_descendants(path).exists?
end
+
+ def delete_conflicting_orphaned_routes
+ conflicting = self.class.iwhere(path: path)
+ conflicting_orphaned_routes = conflicting.select do |route|
+ route.source.nil?
+ end
+
+ conflicting_orphaned_routes.each(&:destroy)
+ end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 7f260f7a96b..96a064697f0 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -118,6 +118,11 @@ class Service < ActiveRecord::Base
nil
end
+ def api_field_names
+ fields.map { |field| field[:name] }
+ .reject { |field_name| field_name =~ /(password|token|key)/ }
+ end
+
def global_fields
fields
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 4484ee9ff4c..9403da98268 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -53,7 +53,10 @@ class User < ActiveRecord::Base
serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize
devise :lockable, :recoverable, :rememberable, :trackable,
- :validatable, :omniauthable, :confirmable, :registerable
+ :validatable, :omniauthable, :confirmable, :registerable
+
+ BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
+ "administrator if you think this is an error.".freeze
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
@@ -217,8 +220,7 @@ class User < ActiveRecord::Base
end
def inactive_message
- "Your account has been blocked. Please contact your GitLab " \
- "administrator if you think this is an error."
+ BLOCKED_MESSAGE
end
end
end
@@ -316,6 +318,8 @@ class User < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation.
def search(query)
+ return none if query.blank?
+
query = query.downcase
order = <<~SQL
@@ -339,6 +343,8 @@ class User < ActiveRecord::Base
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
def search_with_secondary_emails(query)
+ return none if query.blank?
+
query = query.downcase
email_table = Email.arel_table
diff --git a/app/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb
index 229311eb6ee..c226586fba5 100644
--- a/app/presenters/projects/settings/deploy_keys_presenter.rb
+++ b/app/presenters/projects/settings/deploy_keys_presenter.rb
@@ -7,7 +7,7 @@ module Projects
delegate :size, to: :available_public_keys, prefix: true
def new_key
- @key ||= DeployKey.new
+ @key ||= DeployKey.new.tap { |dk| dk.deploy_keys_projects.build }
end
def enabled_keys
diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb
index c75431a79ae..2678f99510c 100644
--- a/app/serializers/deploy_key_entity.rb
+++ b/app/serializers/deploy_key_entity.rb
@@ -3,19 +3,20 @@ class DeployKeyEntity < Grape::Entity
expose :user_id
expose :title
expose :fingerprint
- expose :can_push
expose :destroyed_when_orphaned?, as: :destroyed_when_orphaned
expose :almost_orphaned?, as: :almost_orphaned
expose :created_at
expose :updated_at
- expose :projects, using: ProjectEntity do |deploy_key|
- deploy_key.projects.without_deleted.select { |project| options[:user].can?(:read_project, project) }
+ expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key|
+ deploy_key.deploy_keys_projects
+ .without_project_deleted
+ .select { |deploy_key_project| Ability.allowed?(options[:user], :read_project, deploy_key_project.project) }
end
expose :can_edit
private
def can_edit
- options[:user].can?(:update_deploy_key, object)
+ Ability.allowed?(options[:user], :update_deploy_key, object)
end
end
diff --git a/app/serializers/deploy_keys_project_entity.rb b/app/serializers/deploy_keys_project_entity.rb
new file mode 100644
index 00000000000..568ef5ab75e
--- /dev/null
+++ b/app/serializers/deploy_keys_project_entity.rb
@@ -0,0 +1,4 @@
+class DeployKeysProjectEntity < Grape::Entity
+ expose :can_push
+ expose :project, using: ProjectEntity
+end
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index 997d247be46..74a85e5c9f0 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -13,6 +13,7 @@ module Labels
update_issuables(new_label, batched_ids)
update_issue_board_lists(new_label, batched_ids)
update_priorities(new_label, batched_ids)
+ subscribe_users(new_label, batched_ids)
# Order is important, project labels need to be last
update_project_labels(batched_ids)
end
@@ -26,6 +27,15 @@ module Labels
private
+ def subscribe_users(new_label, label_ids)
+ # users can be subscribed to multiple labels that will be merged into the group one
+ # we want to keep only one subscription / user
+ ids_to_update = Subscription.where(subscribable_id: label_ids, subscribable_type: 'Label')
+ .group(:user_id)
+ .pluck('MAX(id)')
+ Subscription.where(id: ids_to_update).update_all(subscribable_id: new_label.id)
+ end
+
def label_ids_for_merge(new_label)
LabelsFinder
.new(current_user, title: new_label.title, group_id: project.group.id)
@@ -53,7 +63,7 @@ module Labels
end
def update_project_labels(label_ids)
- Label.where(id: label_ids).delete_all
+ Label.where(id: label_ids).destroy_all
end
def clone_label_to_group_label(label)
diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb
index 89dab1dd028..cf687b71d16 100644
--- a/app/services/merge_requests/create_from_issue_service.rb
+++ b/app/services/merge_requests/create_from_issue_service.rb
@@ -54,6 +54,7 @@ module MergeRequests
source_project_id: project.id,
source_branch: branch_name,
target_project_id: project.id,
+ target_branch: ref,
milestone_id: issue.milestone_id
}
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 49cf534dc0d..634bf3bd690 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -1,15 +1,11 @@
module MergeRequests
class CreateService < MergeRequests::BaseService
def execute
- # @project is used to determine whether the user can set the merge request's
- # assignee, milestone and labels. Whether they can depends on their
- # permissions on the target project.
- source_project = @project
- @project = Project.find(params[:target_project_id]) if params[:target_project_id]
+ set_projects!
merge_request = MergeRequest.new
merge_request.target_project = @project
- merge_request.source_project = source_project
+ merge_request.source_project = @source_project
merge_request.source_branch = params[:source_branch]
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
@@ -58,5 +54,25 @@ module MergeRequests
pipelines.order(id: :desc).first
end
+
+ def set_projects!
+ # @project is used to determine whether the user can set the merge request's
+ # assignee, milestone and labels. Whether they can depends on their
+ # permissions on the target project.
+ @source_project = @project
+ @project = Project.find(params[:target_project_id]) if params[:target_project_id]
+
+ # make sure that source/target project ids are not in
+ # params so it can't be overridden later when updating attributes
+ # from params when applying quick actions
+ params.delete(:source_project_id)
+ params.delete(:target_project_id)
+
+ unless can?(current_user, :read_project, @source_project) &&
+ can?(current_user, :read_project, @project)
+
+ raise Gitlab::Access::AccessDeniedError
+ end
+ end
end
end
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 3eb8cfcca9b..6835b14648b 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -11,11 +11,11 @@ module NotificationRecipientService
end
def self.build_recipients(*a)
- Builder::Default.new(*a).recipient_users
+ Builder::Default.new(*a).notification_recipients
end
def self.build_new_note_recipients(*a)
- Builder::NewNote.new(*a).recipient_users
+ Builder::NewNote.new(*a).notification_recipients
end
module Builder
@@ -49,25 +49,24 @@ module NotificationRecipientService
@recipients ||= []
end
- def <<(pair)
- users, type = pair
-
+ def add_recipients(users, type, reason)
if users.is_a?(ActiveRecord::Relation)
users = users.includes(:notification_settings)
end
users = Array(users)
users.compact!
- recipients.concat(users.map { |u| make_recipient(u, type) })
+ recipients.concat(users.map { |u| make_recipient(u, type, reason) })
end
def user_scope
User.includes(:notification_settings)
end
- def make_recipient(user, type)
+ def make_recipient(user, type, reason)
NotificationRecipient.new(
user, type,
+ reason: reason,
project: project,
custom_action: custom_action,
target: target,
@@ -75,14 +74,13 @@ module NotificationRecipientService
)
end
- def recipient_users
- @recipient_users ||=
+ def notification_recipients
+ @notification_recipients ||=
begin
build!
filter!
- users = recipients.map(&:user)
- users.uniq!
- users.freeze
+ recipients = self.recipients.sort_by { |r| NotificationReason.priority(r.reason) }.uniq(&:user)
+ recipients.freeze
end
end
@@ -95,13 +93,13 @@ module NotificationRecipientService
def add_participants(user)
return unless target.respond_to?(:participants)
- self << [target.participants(user), :participating]
+ add_recipients(target.participants(user), :participating, nil)
end
def add_mentions(user, target:)
return unless target.respond_to?(:mentioned_users)
- self << [target.mentioned_users(user), :mention]
+ add_recipients(target.mentioned_users(user), :mention, NotificationReason::MENTIONED)
end
# Get project/group users with CUSTOM notification level
@@ -119,11 +117,11 @@ module NotificationRecipientService
global_users_ids = user_ids_with_project_level_global.concat(user_ids_with_group_level_global)
user_ids += user_ids_with_global_level_custom(global_users_ids, custom_action)
- self << [user_scope.where(id: user_ids), :watch]
+ add_recipients(user_scope.where(id: user_ids), :watch, nil)
end
def add_project_watchers
- self << [project_watchers, :watch]
+ add_recipients(project_watchers, :watch, nil)
end
# Get project users with WATCH notification level
@@ -144,7 +142,7 @@ module NotificationRecipientService
def add_subscribed_users
return unless target.respond_to? :subscribers
- self << [target.subscribers(project), :subscription]
+ add_recipients(target.subscribers(project), :subscription, nil)
end
def user_ids_notifiable_on(resource, notification_level = nil)
@@ -195,7 +193,7 @@ module NotificationRecipientService
return unless target.respond_to? :labels
(labels || target.labels).each do |label|
- self << [label.subscribers(project), :subscription]
+ add_recipients(label.subscribers(project), :subscription, nil)
end
end
end
@@ -222,12 +220,12 @@ module NotificationRecipientService
# Re-assign is considered as a mention of the new assignee
case custom_action
when :reassign_merge_request
- self << [previous_assignee, :mention]
- self << [target.assignee, :mention]
+ add_recipients(previous_assignee, :mention, nil)
+ add_recipients(target.assignee, :mention, NotificationReason::ASSIGNED)
when :reassign_issue
previous_assignees = Array(previous_assignee)
- self << [previous_assignees, :mention]
- self << [target.assignees, :mention]
+ add_recipients(previous_assignees, :mention, nil)
+ add_recipients(target.assignees, :mention, NotificationReason::ASSIGNED)
end
add_subscribed_users
@@ -238,6 +236,12 @@ module NotificationRecipientService
# receive them, too.
add_mentions(current_user, target: target)
+ # Add the assigned users, if any
+ assignees = custom_action == :new_issue ? target.assignees : target.assignee
+ # We use the `:participating` notification level in order to match existing legacy behavior as captured
+ # in existing specs (notification_service_spec.rb ~ line 507)
+ add_recipients(assignees, :participating, NotificationReason::ASSIGNED) if assignees
+
add_labels_subscribers
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index be3b4b2ba07..8c84ccfcc92 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -85,10 +85,11 @@ class NotificationService
recipients.each do |recipient|
mailer.send(
:reassigned_issue_email,
- recipient.id,
+ recipient.user.id,
issue.id,
previous_assignee_ids,
- current_user.id
+ current_user.id,
+ recipient.reason
).deliver_later
end
end
@@ -176,7 +177,7 @@ class NotificationService
action: "resolve_all_discussions")
recipients.each do |recipient|
- mailer.resolved_all_discussions_email(recipient.id, merge_request.id, current_user.id).deliver_later
+ mailer.resolved_all_discussions_email(recipient.user.id, merge_request.id, current_user.id, recipient.reason).deliver_later
end
end
@@ -199,7 +200,7 @@ class NotificationService
recipients = NotificationRecipientService.build_new_note_recipients(note)
recipients.each do |recipient|
- mailer.send(notify_method, recipient.id, note.id).deliver_later
+ mailer.send(notify_method, recipient.user.id, note.id).deliver_later
end
end
@@ -299,7 +300,7 @@ class NotificationService
recipients = NotificationRecipientService.build_recipients(issue, current_user, action: 'moved')
recipients.map do |recipient|
- email = mailer.issue_moved_email(recipient, issue, new_issue, current_user)
+ email = mailer.issue_moved_email(recipient.user, issue, new_issue, current_user, recipient.reason)
email.deliver_later
email
end
@@ -339,16 +340,16 @@ class NotificationService
recipients = NotificationRecipientService.build_recipients(target, target.author, action: "new")
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id).deliver_later
+ mailer.send(method, recipient.user.id, target.id, recipient.reason).deliver_later
end
end
def new_mentions_in_resource_email(target, new_mentioned_users, current_user, method)
recipients = NotificationRecipientService.build_recipients(target, current_user, action: "new")
- recipients = recipients & new_mentioned_users
+ recipients = recipients.select {|r| new_mentioned_users.include?(r.user) }
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
+ mailer.send(method, recipient.user.id, target.id, current_user.id, recipient.reason).deliver_later
end
end
@@ -363,7 +364,7 @@ class NotificationService
)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
+ mailer.send(method, recipient.user.id, target.id, current_user.id, recipient.reason).deliver_later
end
end
@@ -381,10 +382,11 @@ class NotificationService
recipients.each do |recipient|
mailer.send(
method,
- recipient.id,
+ recipient.user.id,
target.id,
previous_assignee_id,
- current_user.id
+ current_user.id,
+ recipient.reason
).deliver_later
end
end
@@ -408,7 +410,7 @@ class NotificationService
recipients = NotificationRecipientService.build_recipients(target, current_user, action: "reopen")
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, status, current_user.id).deliver_later
+ mailer.send(method, recipient.user.id, target.id, status, current_user.id, recipient.reason).deliver_later
end
end
diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb
index 4ca6414b73b..a3d7f5cbed5 100644
--- a/app/services/projects/gitlab_projects_import_service.rb
+++ b/app/services/projects/gitlab_projects_import_service.rb
@@ -26,7 +26,7 @@ module Projects
end
def tmp_filename
- "#{SecureRandom.hex}_#{params[:path]}"
+ SecureRandom.hex
end
def file
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index 690918b4a00..a6b7a6e1416 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -8,7 +8,7 @@ class SystemHooksService
end
def execute_hooks(data, hooks_scope = :all)
- SystemHook.public_send(hooks_scope).find_each do |hook| # rubocop:disable GitlabSecurity/PublicSend
+ SystemHook.hooks_for(hooks_scope).find_each do |hook|
hook.async_execute(data, 'system_hooks')
end
end
@@ -41,8 +41,11 @@ class SystemHooksService
when User
data.merge!(user_data(model))
- if event == :rename
+ case event
+ when :rename
data[:old_username] = model.username_was
+ when :failed_login
+ data[:state] = model.state
end
when ProjectMember
data.merge!(project_member_data(model))
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 30a5aab13bf..06b23cd7076 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -68,21 +68,14 @@ module SystemNoteService
#
# Returns the created Note object
def change_issue_assignees(issue, project, author, old_assignees)
- body =
- if issue.assignees.any? && old_assignees.any?
- unassigned_users = old_assignees - issue.assignees
- added_users = issue.assignees.to_a - old_assignees
-
- text_parts = []
- text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
- text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
-
- text_parts.join(' and ')
- elsif old_assignees.any?
- "removed assignee"
- elsif issue.assignees.any?
- "assigned to #{issue.assignees.map(&:to_reference).to_sentence}"
- end
+ unassigned_users = old_assignees - issue.assignees
+ added_users = issue.assignees.to_a - old_assignees
+
+ text_parts = []
+ text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
+ text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
+
+ body = text_parts.join(' and ')
create_note(NoteSummary.new(issue, project, author, body, action: 'assignee'))
end
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
index 20d90504bd2..e9aefb1fb75 100644
--- a/app/services/test_hooks/base_service.rb
+++ b/app/services/test_hooks/base_service.rb
@@ -9,7 +9,7 @@ module TestHooks
end
def execute
- trigger_key = hook.class::TRIGGERS.key(trigger.to_sym)
+ trigger_key = hook.class.triggers.key(trigger.to_sym)
trigger_data_method = "#{trigger}_data"
if trigger_key.nil? || !self.respond_to?(trigger_data_method, true)
diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb
index 67552edefc9..9016c77b7f0 100644
--- a/app/services/test_hooks/system_service.rb
+++ b/app/services/test_hooks/system_service.rb
@@ -13,5 +13,12 @@ module TestHooks
def repository_update_events_data
Gitlab::DataBuilder::Repository.sample_data
end
+
+ def merge_requests_events_data
+ merge_request = MergeRequest.of_projects(current_user.projects.select(:id)).first
+ throw(:validation_error, 'Ensure one of your projects has merge requests.') unless merge_request.present?
+
+ merge_request.to_hook_data(current_user)
+ end
end
end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 6ebc7c89500..36e589d5aa8 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -113,7 +113,7 @@ class WebHookService
'Content-Type' => 'application/json',
'X-Gitlab-Event' => hook_name.singularize.titleize
}.tap do |hash|
- hash['X-Gitlab-Token'] = hook.token if hook.token.present?
+ hash['X-Gitlab-Token'] = Gitlab::Utils.remove_line_breaks(hook.token) if hook.token.present?
end
end
end
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index a24516355bf..509f559c120 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -4,6 +4,34 @@
%div{ class: container_class }
.admin-dashboard.prepend-top-default
.row
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_projects_path do
+ %h3.text-center
+ Projects:
+ = number_with_delimiter(Project.cached_count)
+ %hr
+ = link_to('New project', new_project_path, class: "btn btn-new")
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_users_path do
+ %h3.text-center
+ Users:
+ = number_with_delimiter(User.count)
+ %hr
+ = link_to 'New user', new_admin_user_path, class: "btn btn-new"
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_groups_path do
+ %h3.text-center
+ Groups:
+ = number_with_delimiter(Group.count)
+ %hr
+ = link_to 'New group', new_admin_group_path, class: "btn btn-new"
+ .row
.col-md-4
.info-well
.well-segment.admin-well.admin-well-statistics
@@ -136,34 +164,6 @@
%span.pull-right
= Gitlab::Database.version
.row
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_projects_path do
- %h3.text-center
- Projects:
- = number_with_delimiter(Project.cached_count)
- %hr
- = link_to('New project', new_project_path, class: "btn btn-new")
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_users_path do
- %h3.text-center
- Users:
- = number_with_delimiter(User.count)
- %hr
- = link_to 'New user', new_admin_user_path, class: "btn btn-new"
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_groups_path do
- %h3.text-center
- Groups:
- = number_with_delimiter(Group.count)
- %hr
- = link_to 'New group', new_admin_group_path, class: "btn btn-new"
- .row
.col-md-4
.info-well
.well-segment.admin-well
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 92370034baa..1420163fd5a 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -12,7 +12,7 @@
%tr
%th.col-sm-2 Title
%th.col-sm-4 Fingerprint
- %th.col-sm-2 Write access allowed
+ %th.col-sm-2 Projects with write access
%th.col-sm-2 Added at
%th.col-sm-2
%tbody
@@ -23,10 +23,8 @@
%td
%code.key-fingerprint= deploy_key.fingerprint
%td
- - if deploy_key.can_push?
- Yes
- - else
- No
+ - deploy_key.projects_with_write_access.each do |project|
+ = link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label'
%td
%span.cgray
added #{time_ago_with_tooltip(deploy_key.created_at)}
diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml
index 645005c6deb..d8f96ed5b0d 100644
--- a/app/views/admin/hooks/_form.html.haml
+++ b/app/views/admin/hooks/_form.html.haml
@@ -38,6 +38,13 @@
%strong Tag push events
%p.light
This URL will be triggered when a new tag is pushed to the repository
+ %div
+ = form.check_box :merge_requests_events, class: 'pull-left'
+ .prepend-left-20
+ = form.label :merge_requests_events, class: 'list-label' do
+ %strong Merge request events
+ %p.light
+ This URL will be triggered when a merge request is created/updated/merged
.form-group
= form.label :enable_ssl_verification, 'SSL verification', class: 'control-label checkbox'
.col-sm-10
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index efb15ccc8df..629b1a9940f 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -13,7 +13,7 @@
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
= f.submit 'Save changes', class: 'btn btn-create'
- = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: @hook
+ = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: @hook
= link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index b6e1df5f3ac..bc02d9969d6 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -22,12 +22,12 @@
- @hooks.each do |hook|
%li
.controls
- = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-sm'
+ = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: hook, button_class: 'btn-sm'
= link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
.monospace= hook.url
%div
- - SystemHook::TRIGGERS.each_value do |event|
+ - SystemHook.triggers.each_value do |event|
- if hook.public_send(event)
%span.label.label-gray= event.to_s.titleize
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index 7066ed12b95..a01676d82a8 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -9,7 +9,12 @@
.nav-controls
- if @all_builds.running_or_pending.any?
- = link_to 'Cancel all', cancel_all_admin_jobs_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+ #stop-jobs-modal
+
+ %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal',
+ target: '#stop-jobs-modal',
+ url: cancel_all_admin_jobs_path } }
+ = s_('AdminArea|Stop all jobs')
.row-content-block.second-block
#{(@scope || 'all').capitalize} jobs
diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml
index ecdf76ef5c5..7a3f3667ac1 100644
--- a/app/views/dashboard/_activity_head.html.haml
+++ b/app/views/dashboard/_activity_head.html.haml
@@ -2,7 +2,7 @@
%ul.nav-links
%li{ class: active_when(params[:filter].nil?) }>
= link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
- Your Projects
+ Your projects
%li{ class: active_when(params[:filter] == 'starred') }>
= link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
- Starred Projects
+ Starred projects
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index 7330f4cb523..a9488df07bd 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -2,10 +2,10 @@
%ul.nav-links
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
= link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
- Your Snippets
+ Your snippets
= nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
- Explore Snippets
+ Explore snippets
- if current_user
.nav-controls.hidden-xs
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 42941acc508..3e85535dae0 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -7,7 +7,7 @@
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
+ = link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index 6087f4a0b37..5ddb3ece1cb 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -8,7 +8,7 @@
.login-body
= render 'devise/sessions/new_ldap', server: server
- if password_authentication_enabled_for_web?
- .login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' }
+ .login-box.tab-pane{ id: 'login-pane', role: 'tabpanel' }
.login-body
= render 'devise/sessions/new_base'
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index 94f19ccd44c..270191f9452 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -7,7 +7,7 @@
= link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab'
- if password_authentication_enabled_for_web?
%li
- = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab'
+ = link_to 'Standard', '#login-pane', 'data-toggle' => 'tab'
- if allow_signup?
%li
= link_to 'Register', '#register-pane', 'data-toggle' => 'tab'
diff --git a/app/views/ide/index.html.haml b/app/views/ide/index.html.haml
index cb413f197de..3dbdfc97654 100644
--- a/app/views/ide/index.html.haml
+++ b/app/views/ide/index.html.haml
@@ -1,10 +1,9 @@
+- @body_class = 'ide'
- page_title 'IDE'
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
- = webpack_bundle_tag 'ide'
-
-.ide-flash-container.flash-container
+ = webpack_bundle_tag 'ide', force_same_domain: true
#ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg')} }
.text-center
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 46727811be4..e7fc83a8d04 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -6,8 +6,10 @@
%h1.title
= link_to root_path, title: 'Dashboard', id: 'logo' do
= brand_header_logo
- %span.logo-text.hidden-xs
- = brand_header_logo_type
+ - logo_text = brand_header_logo_type
+ - if logo_text.present?
+ %span.logo-text.hidden-xs
+ = logo_text
- if current_user
= render "layouts/nav/dashboard"
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 0c27b09f7b1..96aae06a9df 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -1,5 +1,5 @@
-- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
-- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
+- issues_count = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute.count
+- merge_requests_count = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute.count
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
@@ -39,14 +39,14 @@
= sprite_icon('issues')
%span.nav-item-name
Issues
- %span.badge.count= number_with_delimiter(issues.count)
+ %span.badge.count= number_with_delimiter(issues_count)
%ul.sidebar-sub-level-items
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do
= link_to issues_group_path(@group) do
%strong.fly-out-top-item-name
#{ _('Issues') }
- %span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues.count)
+ %span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues_count)
%li.divider.fly-out-top-item
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
= link_to issues_group_path(@group), title: 'List' do
@@ -69,13 +69,13 @@
= sprite_icon('git-merge')
%span.nav-item-name
Merge Requests
- %span.badge.count= number_with_delimiter(merge_requests.count)
+ %span.badge.count= number_with_delimiter(merge_requests_count)
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(path: 'groups#merge_requests', html_options: { class: "fly-out-top-item" } ) do
= link_to merge_requests_group_path(@group) do
%strong.fly-out-top-item-name
#{ _('Merge Requests') }
- %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count)
+ %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests_count)
= nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group) do
.nav-icon-container
diff --git a/app/views/layouts/nav_only.html.haml b/app/views/layouts/nav_only.html.haml
index 6fa4b39dc10..0811211f7b2 100644
--- a/app/views/layouts/nav_only.html.haml
+++ b/app/views/layouts/nav_only.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: I18n.locale, class: page_class }
= render "layouts/head"
- %body{ class: "#{user_application_theme} #{@body_class}", data: { page: body_data_page } }
+ %body{ class: "#{user_application_theme} #{@body_class} nav-only", data: { page: body_data_page } }
= render 'peek/bar'
= render "layouts/header/default"
= render 'shared/outdated_browser'
@@ -10,4 +10,5 @@
= render "layouts/broadcast"
= yield :flash_message
= render "layouts/flash"
- = yield
+ .content{ id: "content-body" }
+ = yield
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 40bf45cece7..ab8b1271212 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -20,7 +20,7 @@
#{link_to "View it on GitLab", @target_url}.
%br
-# Don't link the host in the line below, one link in the email is easier to quickly click than two.
- You're receiving this email because of your account on #{Gitlab.config.gitlab.host}.
+ You're receiving this email because #{notification_reason_text(@reason)}.
If you'd like to receive fewer emails, you can
- if @labels_url
adjust your #{link_to 'label subscriptions', @labels_url}.
diff --git a/app/views/layouts/notify.text.erb b/app/views/layouts/notify.text.erb
index b4ce02eead8..de48f548a1b 100644
--- a/app/views/layouts/notify.text.erb
+++ b/app/views/layouts/notify.text.erb
@@ -9,4 +9,4 @@
<% end -%>
<% end -%>
-You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
+<%= "You're receiving this email because #{notification_reason_text(@reason)}." %>
diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml
index 86ebec0179c..e44506ec9c9 100644
--- a/app/views/profiles/gpg_keys/index.html.haml
+++ b/app/views/profiles/gpg_keys/index.html.haml
@@ -3,12 +3,12 @@
= render 'profiles/head'
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
+ .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
GPG keys allow you to verify signed commits.
- .col-lg-9
+ .col-lg-8
%h5.prepend-top-0
Add a GPG key
%p.profile-settings-content
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index aeae7455a1c..66d1d1e8d44 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -3,23 +3,6 @@
= render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
- .col-lg-4
- %h4.prepend-top-0
- Web IDE (Beta)
- %p Enable the new web IDE on this device to make it possible to open and edit multiple files with a single commit
- .col-lg-8.multi-file-editor-options
- = label_tag do
- .preview.append-bottom-10= image_tag "multi-editor-off.png"
- = f.radio_button :multi_file, "off", checked: true
- Off
- = label_tag do
- .preview.append-bottom-10= image_tag "multi-editor-on.png"
- = f.radio_button :multi_file, "on", checked: false
- On
-
- .col-sm-12
- %hr
-
.col-lg-4.application-theme
%h4.prepend-top-0
GitLab navigation theme
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index c5b1897c492..e759c87bda7 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -30,7 +30,7 @@
%li CI variables
%li Any encrypted tokens
%p
- Once the exported file is ready, you will receive a notification email with a download link.
+ Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- if project.export_project_path
= link_to 'Download export', download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index bd99eb93cc8..d367bd6be7b 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -34,7 +34,7 @@
.form-group.visibility-level-setting
= f.label :visibility_level, class: 'label-light' do
Visibility Level
- = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }
+ = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 03ab1bb59e4..5d48a35dc4c 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -1,5 +1,5 @@
#modal-create-new-dir.modal
- .modal-dialog
+ .modal-dialog.modal-lg
.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 05b7dfe2872..21b6aa4bad9 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -1,5 +1,5 @@
#modal-upload-blob.modal
- .modal-dialog
+ .modal-dialog.modal-lg
.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 8e8c911185a..dab94d10bb1 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -9,44 +9,27 @@
- can_create_snippet = can?(current_user, :create_snippet, @project)
- if can_create_issue
- %li
- = link_to new_project_issue_path(@project) do
- #{ _('New issue') }
+ %li= link_to _('New issue'), new_project_issue_path(@project)
- if merge_project
- %li
- = link_to project_new_merge_request_path(merge_project) do
- #{ _('New merge request') }
+ %li= link_to _('New merge request'), project_new_merge_request_path(merge_project)
- if can_create_snippet
- %li
- = link_to new_project_snippet_path(@project) do
- #{ _('New snippet') }
+ %li= link_to _('New snippet'), new_project_snippet_path(@project)
- if can_create_issue || merge_project || can_create_snippet
%li.divider
- if can?(current_user, :push_code, @project)
- %li
- = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
- #{ _('New file') }
+ %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- unless @project.empty_repo?
- %li
- = link_to new_project_branch_path(@project) do
- #{ _('New branch') }
- %li
- = link_to new_project_tag_path(@project) do
- #{ _('New tag') }
+ %li= link_to _('New branch'), new_project_branch_path(@project)
+ %li= link_to _('New tag'), new_project_tag_path(@project)
- elsif current_user && current_user.already_forked?(@project)
- %li
- = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
- #{ _('New file') }
+ %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- elsif can?(current_user, :fork_project, @project)
- %li
- - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
- notice: edit_in_new_fork_notice,
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
- continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('New file') }
+ - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
+ %li= link_to _('New file'), fork_path, method: :post
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 09934c09865..461129a3e0e 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -64,6 +64,12 @@
.commit-info.branches
%i.fa.fa-spinner.fa-spin
+ .well-segment.merge-request-info
+ .icon-container
+ = custom_icon('mr_bold')
+ %span.commit-info.merge-requests{ 'data-project-commit-path' => merge_requests_project_commit_path(@project, @commit.id, format: :json) }
+ = icon('spinner spin')
+
- if @commit.last_pipeline
- last_pipeline = @commit.last_pipeline
.well-segment.pipeline-info
diff --git a/app/views/projects/commits/_commit.atom.builder b/app/views/projects/commits/_commit.atom.builder
index d806acdda13..04914888763 100644
--- a/app/views/projects/commits/_commit.atom.builder
+++ b/app/views/projects/commits/_commit.atom.builder
@@ -1,7 +1,7 @@
xml.entry do
xml.id project_commit_url(@project, id: commit.id)
xml.link href: project_commit_url(@project, id: commit.id)
- xml.title truncate(commit.title, length: 80)
+ xml.title truncate(commit.title, length: 80, escape: false)
xml.updated commit.committed_date.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email))
@@ -10,5 +10,5 @@ xml.entry do
xml.email commit.author_email
end
- xml.summary markdown(commit.description, pipeline: :single_line)
+ xml.summary markdown(commit.description, pipeline: :single_line), type: 'html'
end
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index edaa3a1119e..c363180d0db 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -10,13 +10,15 @@
%p.light.append-bottom-0
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh/README")
- .form-group
- .checkbox
- = f.label :can_push do
- = f.check_box :can_push
- %strong Write access allowed
- .form-group
- %p.light.append-bottom-0
- Allow this key to push to repository as well? (Default only allows pull access.)
+
+ = f.fields_for :deploy_keys_projects do |deploy_keys_project_form|
+ .form-group
+ .checkbox
+ = deploy_keys_project_form.label :can_push do
+ = deploy_keys_project_form.check_box :can_push
+ %strong Write access allowed
+ .form-group
+ %p.light.append-bottom-0
+ Allow this key to push to repository as well? (Default only allows pull access.)
= f.submit "Add key", class: "btn-create btn"
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 111cbcda266..21a4702a2a9 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -31,11 +31,11 @@
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
- = custom_icon('icon_fork')
+ = sprite_icon('fork', size: 12)
%span Fork
- else
= link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-new' do
- = custom_icon('icon_fork')
+ = sprite_icon('fork', size: 12)
%span Fork
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index b1219f019d7..dcc1f0e3fbe 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -12,7 +12,7 @@
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
= f.submit 'Save changes', class: 'btn btn-create'
- = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: @hook
+ = render 'shared/web_hooks/test_button', triggers: ProjectHook.triggers, hook: @hook
= link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml
index c66313bdbf3..311934d9c33 100644
--- a/app/views/projects/jobs/_empty_state.html.haml
+++ b/app/views/projects/jobs/_empty_state.html.haml
@@ -1,7 +1,7 @@
- illustration = local_assigns.fetch(:illustration)
- illustration_size = local_assigns.fetch(:illustration_size)
- title = local_assigns.fetch(:title)
-- content = local_assigns.fetch(:content)
+- content = local_assigns.fetch(:content, nil)
- action = local_assigns.fetch(:action, nil)
.row.empty-state
@@ -11,7 +11,8 @@
.col-xs-12
.text-content
%h4.text-center= title
- %p= content
+ - if content
+ %p= content
- if action
.text-center
= action
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index a71333497e6..e779473c239 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -24,7 +24,7 @@
- elsif @build.has_expiring_artifacts?
%p.build-detail-row
The artifacts will be removed in
- %span.js-artifacts-remove= @build.artifacts_expire_at
+ %span= time_ago_in_words @build.artifacts_expire_at
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
diff --git a/app/views/projects/jobs/_table.html.haml b/app/views/projects/jobs/_table.html.haml
index 82806f022ee..d124d3ebfc1 100644
--- a/app/views/projects/jobs/_table.html.haml
+++ b/app/views/projects/jobs/_table.html.haml
@@ -22,4 +22,4 @@
= render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, pipeline_link: true, stage: true, allow_retry: true, admin: admin }
- = paginate builds, theme: 'gitlab'
+ = paginate_collection(builds)
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 8b05440fc78..1e6d6f67e66 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -93,14 +93,13 @@
illustration: 'illustrations/manual_action.svg',
illustration_size: 'svg-394',
title: _('This job requires a manual action'),
- content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.'),
- action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), class: 'btn btn-primary', title: _('Trigger this manual action') )
+ content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'),
+ action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), method: :post, class: 'btn btn-primary', title: _('Trigger this manual action') )
- else
= render 'empty_state',
illustration: 'illustrations/job_not_triggered.svg',
illustration_size: 'svg-306',
- title: _('This job has not been triggered yet'),
- content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered.')
+ title: _('This job has not been triggered yet')
= render "sidebar"
diff --git a/app/views/projects/merge_requests/creations/_diffs.html.haml b/app/views/projects/merge_requests/creations/_diffs.html.haml
index 627fc4e9671..5b70e894b39 100644
--- a/app/views/projects/merge_requests/creations/_diffs.html.haml
+++ b/app/views/projects/merge_requests/creations/_diffs.html.haml
@@ -1 +1,5 @@
-= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false
+- if @merge_request.can_be_created
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false
+- else
+ .nothing-here-block
+ This merge request cannot be created.
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 4b5fa28078a..376ac377562 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -15,7 +15,7 @@
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
= f.hidden_field :target_project_id
- = f.hidden_field :target_branch
+ = f.hidden_field :target_branch, id: ''
.mr-compare.merge-request.js-merge-request-new-submit{ 'data-mr-submit-action': "#{j params[:tab].presence || 'new'}" }
- if @commits.empty?
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 2f56630c22e..61ae0ebbce6 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -4,8 +4,6 @@
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag 'project_new'
.project-edit-container
.project-edit-errors
diff --git a/app/views/projects/protected_branches/shared/_dropdown.html.haml b/app/views/projects/protected_branches/shared/_dropdown.html.haml
index 6e9c473494e..74435236808 100644
--- a/app/views/projects/protected_branches/shared/_dropdown.html.haml
+++ b/app/views/projects/protected_branches/shared/_dropdown.html.haml
@@ -10,6 +10,6 @@
%ul.dropdown-footer-list
%li
- %button{ class: "create-new-protected-branch-button js-create-new-protected-branch", title: "New Protected Branch" }
+ %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item", title: "New Protected Branch" }
Create wildcard
%code
diff --git a/app/views/projects/protected_tags/shared/_dropdown.html.haml b/app/views/projects/protected_tags/shared/_dropdown.html.haml
index 9b6923210f7..f0d7dcccd36 100644
--- a/app/views/projects/protected_tags/shared/_dropdown.html.haml
+++ b/app/views/projects/protected_tags/shared/_dropdown.html.haml
@@ -10,6 +10,6 @@
%ul.dropdown-footer-list
%li
- %button{ class: "create-new-protected-tag-button js-create-new-protected-tag", title: "New Protected Tag" }
+ %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item", title: "New Protected Tag" }
Create wildcard
%code
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index a4e820628f3..67607e4e9c6 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -23,6 +23,3 @@
%h4.underlined-title Available shared Runners : #{@shared_runners_count}
%ul.bordered-list.available-shared-runners
= render partial: 'projects/runners/runner', collection: @shared_runners, as: :runner
- - if @shared_runners_count > 10
- .light
- and #{@shared_runners_count - 10} more...
diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml
index 82516cb4bcf..cd003107d66 100644
--- a/app/views/projects/settings/integrations/_project_hook.html.haml
+++ b/app/views/projects/settings/integrations/_project_hook.html.haml
@@ -3,14 +3,14 @@
.col-md-8.col-lg-7
%strong.light-header= hook.url
%div
- - ProjectHook::TRIGGERS.each_value do |event|
+ - ProjectHook.triggers.each_value do |event|
- if hook.public_send(event)
%span.label.label-gray.deploy-project-label= event.to_s.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
%span.append-right-10.inline
SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
= link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm'
- = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-sm'
+ = render 'shared/web_hooks/test_button', triggers: ProjectHook.triggers, hook: hook, button_class: 'btn-sm'
= link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do
%span.sr-only Remove
= icon('trash')
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index d1ecef39475..05539dfed7c 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -24,6 +24,8 @@
.add-to-tree-dropdown
%ul.dropdown-menu
- if can_edit_tree?
+ %li.dropdown-header
+ #{ _('This directory') }
%li
= link_to project_new_blob_path(@project, @id) do
#{ _('New file') }
@@ -60,6 +62,8 @@
#{ _('New directory') }
%li.divider
+ %li.dropdown-header
+ #{ _('This repository') }
%li
= link_to new_project_branch_path(@project) do
#{ _('New branch') }
diff --git a/app/views/shared/deploy_keys/_form.html.haml b/app/views/shared/deploy_keys/_form.html.haml
index e6075c3ae3a..87c2965bb21 100644
--- a/app/views/shared/deploy_keys/_form.html.haml
+++ b/app/views/shared/deploy_keys/_form.html.haml
@@ -1,5 +1,6 @@
- form = local_assigns.fetch(:form)
- deploy_key = local_assigns.fetch(:deploy_key)
+- deploy_keys_project = deploy_key.deploy_keys_project_for(@project)
= form_errors(deploy_key)
@@ -20,11 +21,13 @@
.col-sm-10
= form.text_field :fingerprint, class: 'form-control', readonly: 'readonly'
-.form-group
- .control-label
- .col-sm-10
- = form.label :can_push do
- = form.check_box :can_push
- %strong Write access allowed
- %p.light.append-bottom-0
- Allow this key to push to repository as well? (Default only allows pull access.)
+- if deploy_keys_project.present?
+ = form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form|
+ .form-group
+ .control-label
+ .col-sm-10
+ = deploy_keys_project_form.label :can_push do
+ = deploy_keys_project_form.check_box :can_push
+ %strong Write access allowed
+ %p.light.append-bottom-0
+ Allow this key to push to repository as well? (Default only allows pull access.)
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 8442d7ff4a2..7704c88905b 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -22,7 +22,7 @@
= render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true
.filter-item.inline.labels-filter
- = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
+ = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
- if issuable_filter_present?
.filter-item.inline.reset-filters
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index e0009a35b9f..cc00c3c0bfd 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -34,7 +34,7 @@
Milestone
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.hide-collapsed
- if issuable.milestone
= link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_title(issuable.milestone), data: { container: "body", html: 1 }
@@ -60,7 +60,7 @@
Due date
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.hide-collapsed
%span.value-content
- if issuable.due_date
@@ -95,7 +95,7 @@
Labels
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
- if selected_labels.any?
- selected_labels.each do |label|
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index 58782fa5f58..0fca4162ec9 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -13,7 +13,7 @@
Assignee
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
- if !signed_in
%a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
= sidebar_gutter_toggle_icon
diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml
index 203d2adc8db..9a589387255 100644
--- a/app/views/shared/issuable/form/_branch_chooser.html.haml
+++ b/app/views/shared/issuable/form/_branch_chooser.html.haml
@@ -15,11 +15,10 @@
= form.label :target_branch, class: 'control-label'
.col-sm-10.target-branch-select-dropdown-container
.issuable-form-select-holder
- = form.select(:target_branch, issuable.target_branches,
- { include_blank: true },
+ = form.hidden_field(:target_branch,
{ class: 'target_branch js-target-branch-select ref-name',
disabled: issuable.new_record?,
- data: { placeholder: "Select branch" }})
+ data: { placeholder: "Select branch", endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
- if issuable.new_record?
&nbsp;
= link_to 'Change branches', mr_change_branches_path(issuable)
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 63aa4e29ec9..2a75b46d376 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -52,7 +52,7 @@
= render_project_pipeline_status(project.pipeline_status)
- if forks
%span.prepend-left-10
- = sprite_icon('fork')
+ = sprite_icon('fork', size: 12)
= number_with_delimiter(project.forks_count)
- if stars
%span.prepend-left-10
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 1f0e7629fb4..ad4d39b4aa1 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -50,7 +50,7 @@
= form.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20
= form.label :merge_requests_events, class: 'list-label' do
- %strong Merge Request events
+ %strong Merge request events
%p.light
This URL will be triggered when a merge request is created/updated/merged
%li
diff --git a/app/workers/concerns/project_import_options.rb b/app/workers/concerns/project_import_options.rb
index 10b971344f7..ef23990ad97 100644
--- a/app/workers/concerns/project_import_options.rb
+++ b/app/workers/concerns/project_import_options.rb
@@ -1,9 +1,9 @@
module ProjectImportOptions
extend ActiveSupport::Concern
- included do
- IMPORT_RETRY_COUNT = 5
+ IMPORT_RETRY_COUNT = 5
+ included do
sidekiq_options retry: IMPORT_RETRY_COUNT, status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
# We only want to mark the project as failed once we exhausted all retries
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index d1c57b82681..07584fab7c8 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -17,10 +17,7 @@ class RepositoryForkWorker
project.repository_storage_path, project.disk_path)
raise "Unable to fork project #{project_id} for repository #{source_disk_path} -> #{project.disk_path}" unless result
- project.repository.after_import
- raise "Project #{project_id} had an invalid repository after fork" unless project.valid_repo?
-
- project.import_finish
+ project.after_import
end
private
diff --git a/bin/profile-url b/bin/profile-url
new file mode 100755
index 00000000000..d8d09641624
--- /dev/null
+++ b/bin/profile-url
@@ -0,0 +1,57 @@
+#!/usr/bin/env ruby
+require 'optparse'
+
+options = {}
+
+opt_parser = OptionParser.new do |opt|
+ opt.banner = <<DOCSTRING
+Profile a URL on this GitLab instance.
+
+Usage:
+ #{__FILE__} url --output=<profile-html> --sql=<sql-log> [--user=<user>] [--post=<post-data>]
+
+Example:
+ #{__FILE__} /dashboard/issues --output=dashboard-profile.html --sql=dashboard.log --user=root
+DOCSTRING
+ opt.separator ''
+ opt.separator 'Options:'
+
+ opt.on('-o', '--output=/tmp/profile.html', 'profile output filename') do |output|
+ options[:profile_output] = output
+ end
+
+ opt.on('-s', '--sql=/tmp/profile_sql.txt', 'SQL output filename') do |sql|
+ options[:sql_output] = sql
+ end
+
+ opt.on('-u', '--user=root', 'User to authenticate as') do |username|
+ options[:username] = username
+ end
+
+ opt.on('-p', "--post='user=john&pass=test'", 'Send HTTP POST data') do |post_data|
+ options[:post_data] = post_data
+ end
+end
+
+opt_parser.parse!
+options[:url] = ARGV[0]
+
+if options[:url].nil? ||
+ options[:profile_output].nil? ||
+ options[:sql_output].nil?
+ puts opt_parser
+ exit
+end
+
+require File.expand_path('../config/environment', File.dirname(__FILE__))
+
+result = Gitlab::Profiler.profile(options[:url],
+ logger: Logger.new(options[:sql_output]),
+ post_data: options[:post_data],
+ user: User.find_by_username(options[:username]),
+ private_token: ENV['PRIVATE_TOKEN'])
+
+printer = RubyProf::CallStackPrinter.new(result)
+file = File.open(options[:profile_output], 'w')
+printer.print(file)
+file.close
diff --git a/changelogs/unreleased-ee/4378-fix-cluster-js-not-running-on-update-page.yml b/changelogs/unreleased-ee/4378-fix-cluster-js-not-running-on-update-page.yml
new file mode 100644
index 00000000000..bbb6cbd05be
--- /dev/null
+++ b/changelogs/unreleased-ee/4378-fix-cluster-js-not-running-on-update-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix JavaScript bundle running on Cluster update/destroy pages
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/13695-order-contributors-in-api.yml b/changelogs/unreleased/13695-order-contributors-in-api.yml
deleted file mode 100644
index 26bf8650a4a..00000000000
--- a/changelogs/unreleased/13695-order-contributors-in-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds ordering to projects contributors in API
-merge_request: 15469
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/15832-fix-access-level-update-for-requesters.yml b/changelogs/unreleased/15832-fix-access-level-update-for-requesters.yml
deleted file mode 100644
index 9d6c958cb3e..00000000000
--- a/changelogs/unreleased/15832-fix-access-level-update-for-requesters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix error that was preventing users to change the access level of access requests for Groups or Projects
-merge_request: 15832
-author:
-type: fixed
diff --git a/changelogs/unreleased/15922-validate-file-status-when-commiting-multiple-files.yml b/changelogs/unreleased/15922-validate-file-status-when-commiting-multiple-files.yml
deleted file mode 100644
index db2bd6e692b..00000000000
--- a/changelogs/unreleased/15922-validate-file-status-when-commiting-multiple-files.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Validate file status when commiting multiple files'
-merge_request: 15922
-author:
-type: added
diff --git a/changelogs/unreleased/15955-improve-search-query.yml b/changelogs/unreleased/15955-improve-search-query.yml
deleted file mode 100644
index 80cb8af617f..00000000000
--- a/changelogs/unreleased/15955-improve-search-query.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve search query for merge requests.
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml b/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml
deleted file mode 100644
index 833650559a3..00000000000
--- a/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Ignore lost+found folder during backup on a volume"
-merge_request: 16036
-author: Julien Millau
-type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml b/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml
new file mode 100644
index 00000000000..e94b4f8bb26
--- /dev/null
+++ b/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml
@@ -0,0 +1,5 @@
+---
+title: "Update 'removed assignee' note to include old assignee reference"
+merge_request: 16301
+author: Maurizio De Santis
+type: changed
diff --git a/changelogs/unreleased/16468-add-fast-blank.yml b/changelogs/unreleased/16468-add-fast-blank.yml
new file mode 100644
index 00000000000..ef68888ae33
--- /dev/null
+++ b/changelogs/unreleased/16468-add-fast-blank.yml
@@ -0,0 +1,5 @@
+---
+title: "Add fast-blank"
+merge_request: 16468
+author:
+type: performance
diff --git a/changelogs/unreleased/19493-fork-does-not-protect-default-branch.yml b/changelogs/unreleased/19493-fork-does-not-protect-default-branch.yml
new file mode 100644
index 00000000000..962f918e9db
--- /dev/null
+++ b/changelogs/unreleased/19493-fork-does-not-protect-default-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Makes forking protect default branch on completion
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/20035-pause-resume-runners.yml b/changelogs/unreleased/20035-pause-resume-runners.yml
deleted file mode 100644
index 98757e60683..00000000000
--- a/changelogs/unreleased/20035-pause-resume-runners.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add pause/resume button to project runners
-merge_request: 16032
-author: Mario de la Ossa
-type: added
diff --git a/changelogs/unreleased/24347-dont-post-system-note-when-branch-creation-fails.yml b/changelogs/unreleased/24347-dont-post-system-note-when-branch-creation-fails.yml
deleted file mode 100644
index 61153ad4f1a..00000000000
--- a/changelogs/unreleased/24347-dont-post-system-note-when-branch-creation-fails.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix when branch creation fails don't post system note
-merge_request:
-author: Mateusz Bajorski
-type: fixed
diff --git a/changelogs/unreleased/25317-prioritize-author-date-over-commit.yml b/changelogs/unreleased/25317-prioritize-author-date-over-commit.yml
deleted file mode 100644
index a5f6d316a7d..00000000000
--- a/changelogs/unreleased/25317-prioritize-author-date-over-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show authored date rather than committed date on the commit list
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/26296-update-styling-disabled-buttons.yml b/changelogs/unreleased/26296-update-styling-disabled-buttons.yml
new file mode 100644
index 00000000000..5fa109d75e0
--- /dev/null
+++ b/changelogs/unreleased/26296-update-styling-disabled-buttons.yml
@@ -0,0 +1,5 @@
+---
+title: Set standard disabled state for all buttons
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/28004-consider-refactoring-member-view-by-using-presenter.yml b/changelogs/unreleased/28004-consider-refactoring-member-view-by-using-presenter.yml
deleted file mode 100644
index 0e91d4ae403..00000000000
--- a/changelogs/unreleased/28004-consider-refactoring-member-view-by-using-presenter.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Refactor member view using a Presenter
-merge_request: 9645
-author: TM Lee
diff --git a/changelogs/unreleased/31995-project-limit-default-fix.yml b/changelogs/unreleased/31995-project-limit-default-fix.yml
deleted file mode 100644
index 4f25eb34b45..00000000000
--- a/changelogs/unreleased/31995-project-limit-default-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: User#projects_limit remove DB default and added NOT NULL constraint
-merge_request: 16165
-author: Mario de la Ossa
-type: fixed
diff --git a/changelogs/unreleased/32364-updating-slack-notification-not-working-by-api.yml b/changelogs/unreleased/32364-updating-slack-notification-not-working-by-api.yml
deleted file mode 100644
index e3fae55c6f0..00000000000
--- a/changelogs/unreleased/32364-updating-slack-notification-not-working-by-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support new chat notifications parameters in Services API
-merge_request: 11435
-author:
-type: added
diff --git a/changelogs/unreleased/33028-event-tag-links.yml b/changelogs/unreleased/33028-event-tag-links.yml
deleted file mode 100644
index 1d674200dcd..00000000000
--- a/changelogs/unreleased/33028-event-tag-links.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix tags in the Activity tab not being clickable
-merge_request: 15996
-author: Mario de la Ossa
-type: fixed
diff --git a/changelogs/unreleased/33609-hide-pagination.yml b/changelogs/unreleased/33609-hide-pagination.yml
deleted file mode 100644
index 3586b091cb1..00000000000
--- a/changelogs/unreleased/33609-hide-pagination.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disable Vue pagination when only one page of content is available
-merge_request: 15999
-author: Mario de la Ossa
-type: fixed
diff --git a/changelogs/unreleased/33926-update-issuable-icons.yml b/changelogs/unreleased/33926-update-issuable-icons.yml
deleted file mode 100644
index 87076dde545..00000000000
--- a/changelogs/unreleased/33926-update-issuable-icons.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update issuable status icons
-merge_request: 15898
-author:
-type: changed
diff --git a/changelogs/unreleased/34055-issues-enabled-filter-misbehavior.yml b/changelogs/unreleased/34055-issues-enabled-filter-misbehavior.yml
new file mode 100644
index 00000000000..09e2af1e4d3
--- /dev/null
+++ b/changelogs/unreleased/34055-issues-enabled-filter-misbehavior.yml
@@ -0,0 +1,6 @@
+---
+title: Fix the Projects API with_issues_enabled filter behaving incorrectly
+ any user
+merge_request: 12724
+author: Jan Christophersen
+type: fixed
diff --git a/changelogs/unreleased/34252-trailing-plus.yml b/changelogs/unreleased/34252-trailing-plus.yml
new file mode 100644
index 00000000000..fce17cb6ab9
--- /dev/null
+++ b/changelogs/unreleased/34252-trailing-plus.yml
@@ -0,0 +1,5 @@
+---
+title: Allow trailing + on labels in board filters
+merge_request: 16490
+author:
+type: fixed
diff --git a/changelogs/unreleased/34534-switch-to-axios.yml b/changelogs/unreleased/34534-switch-to-axios.yml
deleted file mode 100644
index 1200272c9eb..00000000000
--- a/changelogs/unreleased/34534-switch-to-axios.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix some POST/DELETE requests in IE by switching some bundles to Axios for Ajax requests
-merge_request: 15951
-author:
-type: fixed
diff --git a/changelogs/unreleased/36020-private-npm-modules.yml b/changelogs/unreleased/36020-private-npm-modules.yml
deleted file mode 100644
index 5c2585a602e..00000000000
--- a/changelogs/unreleased/36020-private-npm-modules.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Do not generate NPM links for private NPM modules in blob view
-merge_request: 16002
-author: Mario de la Ossa
-type: added
diff --git a/changelogs/unreleased/36571-ignore-root-in-repo.yml b/changelogs/unreleased/36571-ignore-root-in-repo.yml
new file mode 100644
index 00000000000..396e82be51b
--- /dev/null
+++ b/changelogs/unreleased/36571-ignore-root-in-repo.yml
@@ -0,0 +1,5 @@
+---
+title: Ignore leading slashes when searching for files within context of repository.
+merge_request:
+author: Andrew McCallum
+type: fixed
diff --git a/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml b/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml
deleted file mode 100644
index 6af9ac4b099..00000000000
--- a/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Default merge request title is set correctly again when external issue tracker is activated
-merge_request: 16356
-author: Ben305
-type: fixed
diff --git a/changelogs/unreleased/36782-replace-team-user-role-with-add_role-user-in-specs.yml b/changelogs/unreleased/36782-replace-team-user-role-with-add_role-user-in-specs.yml
deleted file mode 100644
index 8773ac73a75..00000000000
--- a/changelogs/unreleased/36782-replace-team-user-role-with-add_role-user-in-specs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Replace '.team << [user, role]' with 'add_role(user)' in specs
-merge_request: 16069
-author: "@blackst0ne"
-type: other
diff --git a/changelogs/unreleased/36958-enable-ordering-projects-subgroups-by-name.yml b/changelogs/unreleased/36958-enable-ordering-projects-subgroups-by-name.yml
deleted file mode 100644
index 8348e3e8ceb..00000000000
--- a/changelogs/unreleased/36958-enable-ordering-projects-subgroups-by-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable ordering of groups and their children by name
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/37199-labels-fix.yml b/changelogs/unreleased/37199-labels-fix.yml
new file mode 100644
index 00000000000..bd70babb73d
--- /dev/null
+++ b/changelogs/unreleased/37199-labels-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Keep subscribers when promoting labels to group labels
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml b/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml
deleted file mode 100644
index abf98cd2af4..00000000000
--- a/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix ANSI 256 bold colors in pipelines job output
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/37898-increase-readability-of-colored-text-in-job-output-log.yml b/changelogs/unreleased/37898-increase-readability-of-colored-text-in-job-output-log.yml
new file mode 100644
index 00000000000..813b9ab81fa
--- /dev/null
+++ b/changelogs/unreleased/37898-increase-readability-of-colored-text-in-job-output-log.yml
@@ -0,0 +1,5 @@
+---
+title: increase-readability-of-colored-text-in-job-output-log
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/38019-hide-runner-token.yml b/changelogs/unreleased/38019-hide-runner-token.yml
deleted file mode 100644
index 11ae0a685ef..00000000000
--- a/changelogs/unreleased/38019-hide-runner-token.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide runner token in CI/CD settings page
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/38030-add-graph-value-to-hover.yml b/changelogs/unreleased/38030-add-graph-value-to-hover.yml
deleted file mode 100644
index 233db2b19c9..00000000000
--- a/changelogs/unreleased/38030-add-graph-value-to-hover.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display graph values on hover within monitoring page
-merge_request: 16261
-author:
-type: changed
diff --git a/changelogs/unreleased/38145_ux_issues_in_system_info_page.yml b/changelogs/unreleased/38145_ux_issues_in_system_info_page.yml
deleted file mode 100644
index d2358750518..00000000000
--- a/changelogs/unreleased/38145_ux_issues_in_system_info_page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes the wording of headers in system info page
-merge_request: 15802
-author: Gilbert Roulot
-type: fixed
diff --git a/changelogs/unreleased/38239-update-toggle-design.yml b/changelogs/unreleased/38239-update-toggle-design.yml
deleted file mode 100644
index 4d9034e8515..00000000000
--- a/changelogs/unreleased/38239-update-toggle-design.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update feature toggle design to use icons and make it i18n friendly
-merge_request: 15904
-author:
-type: changed
diff --git a/changelogs/unreleased/38318-search-merge-requests-with-api.yml b/changelogs/unreleased/38318-search-merge-requests-with-api.yml
deleted file mode 100644
index d8b2f1f25c8..00000000000
--- a/changelogs/unreleased/38318-search-merge-requests-with-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add optional search param for Merge Requests API
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/38540-ssh-env-file.yml b/changelogs/unreleased/38540-ssh-env-file.yml
new file mode 100644
index 00000000000..5ada0ede76d
--- /dev/null
+++ b/changelogs/unreleased/38540-ssh-env-file.yml
@@ -0,0 +1,6 @@
+---
+title: 'Closes #38540 - Remove .ssh/environment file that now breaks the gitlab:check
+ rake task'
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/38541-cancel-alignment.yml b/changelogs/unreleased/38541-cancel-alignment.yml
deleted file mode 100644
index c6d5136dd57..00000000000
--- a/changelogs/unreleased/38541-cancel-alignment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: fix button alignment on MWPS component
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/38596-fix-backspace-visual-token-clearing.yml b/changelogs/unreleased/38596-fix-backspace-visual-token-clearing.yml
deleted file mode 100644
index 4a9d0b66a8c..00000000000
--- a/changelogs/unreleased/38596-fix-backspace-visual-token-clearing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clears visual token on second backspace
-merge_request:
-author: Martin Wortschack
-type: fixed
diff --git a/changelogs/unreleased/38893-banzai-upload-filter-relative-urls.yml b/changelogs/unreleased/38893-banzai-upload-filter-relative-urls.yml
deleted file mode 100644
index 9ab0a0159e9..00000000000
--- a/changelogs/unreleased/38893-banzai-upload-filter-relative-urls.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use relative URLs when linking to uploaded files
-merge_request: 15751
-author:
-type: other
diff --git a/changelogs/unreleased/39246-fork-and-import-jobs-should-only-be-marked-as-failed-when-the-number-of-retries-was-exhausted.yml b/changelogs/unreleased/39246-fork-and-import-jobs-should-only-be-marked-as-failed-when-the-number-of-retries-was-exhausted.yml
deleted file mode 100644
index ce238a2c79f..00000000000
--- a/changelogs/unreleased/39246-fork-and-import-jobs-should-only-be-marked-as-failed-when-the-number-of-retries-was-exhausted.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only mark import and fork jobs as failed once all Sidekiq retries get exhausted
-merge_request: 15844
-author:
-type: changed
diff --git a/changelogs/unreleased/39298-list-of-avatars-2.yml b/changelogs/unreleased/39298-list-of-avatars-2.yml
deleted file mode 100644
index e2095561c0e..00000000000
--- a/changelogs/unreleased/39298-list-of-avatars-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: List of avatars should never show +1
-merge_request: 15972
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/39608-comment-on-image-discussions-tab-alignment.yml b/changelogs/unreleased/39608-comment-on-image-discussions-tab-alignment.yml
deleted file mode 100644
index 5021fe88caf..00000000000
--- a/changelogs/unreleased/39608-comment-on-image-discussions-tab-alignment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update comment on image cursor and icons
-merge_request: 15760
-author:
-type: fixed
diff --git a/changelogs/unreleased/3968-protected-branch-is-not-set-for-default-branch-on-import.yml b/changelogs/unreleased/3968-protected-branch-is-not-set-for-default-branch-on-import.yml
deleted file mode 100644
index e972ac6d54a..00000000000
--- a/changelogs/unreleased/3968-protected-branch-is-not-set-for-default-branch-on-import.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Protected branch is now created for default branch on import
-merge_request: 16198
-author:
-type: fixed
diff --git a/changelogs/unreleased/39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled.yml b/changelogs/unreleased/39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled.yml
deleted file mode 100644
index d8fd1f14bd4..00000000000
--- a/changelogs/unreleased/39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Implement checking GCP project billing status in cluster creation form.
-merge_request: 15665
-author:
-type: changed
diff --git a/changelogs/unreleased/40029-better-error-handling-on-issuable-templates.yml b/changelogs/unreleased/40029-better-error-handling-on-issuable-templates.yml
new file mode 100644
index 00000000000..519f411d642
--- /dev/null
+++ b/changelogs/unreleased/40029-better-error-handling-on-issuable-templates.yml
@@ -0,0 +1,5 @@
+---
+title: Stop loading spinner on error of issuable templates
+merge_request: 16600
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/40031-include-assset_sync-gem.yml b/changelogs/unreleased/40031-include-assset_sync-gem.yml
deleted file mode 100644
index 93ce565b32c..00000000000
--- a/changelogs/unreleased/40031-include-assset_sync-gem.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add assets_sync gem to Gemfile
-merge_request: 15734
-author:
-type: added
diff --git a/changelogs/unreleased/40040-decouple-multi-file-editor-from-file-list.yml b/changelogs/unreleased/40040-decouple-multi-file-editor-from-file-list.yml
deleted file mode 100644
index e2fade2bfd9..00000000000
--- a/changelogs/unreleased/40040-decouple-multi-file-editor-from-file-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds the multi file editor as a new beta feature
-merge_request: 15430
-author:
-type: feature
diff --git a/changelogs/unreleased/40063-markdown-editor-improvements.yml b/changelogs/unreleased/40063-markdown-editor-improvements.yml
deleted file mode 100644
index fa2f09408b4..00000000000
--- a/changelogs/unreleased/40063-markdown-editor-improvements.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide markdown toolbar in preview mode
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/40190-fix-slash-commands-dropdown-description-mis-alignement-on-firefox.yml b/changelogs/unreleased/40190-fix-slash-commands-dropdown-description-mis-alignement-on-firefox.yml
deleted file mode 100644
index 71a606ff607..00000000000
--- a/changelogs/unreleased/40190-fix-slash-commands-dropdown-description-mis-alignement-on-firefox.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Fix slash commands dropdown description mis-alignment on Firefox"
-merge_request: 16125
-author: Maurizio De Santis
-type: fixed
diff --git a/changelogs/unreleased/40228-verify-integrity-of-repositories.yml b/changelogs/unreleased/40228-verify-integrity-of-repositories.yml
deleted file mode 100644
index 261d48652db..00000000000
--- a/changelogs/unreleased/40228-verify-integrity-of-repositories.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix gitlab-rake gitlab:import:repos import schedule
-merge_request: 15931
-author:
-type: fixed
diff --git a/changelogs/unreleased/40274-user-settings-breadcrumbs.yml b/changelogs/unreleased/40274-user-settings-breadcrumbs.yml
deleted file mode 100644
index 1f381668aca..00000000000
--- a/changelogs/unreleased/40274-user-settings-breadcrumbs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix breadcrumbs in User Settings
-merge_request: 16172
-author: rfwatson
-type: fixed
diff --git a/changelogs/unreleased/40301-rebase.yml b/changelogs/unreleased/40301-rebase.yml
deleted file mode 100644
index 1c0fc0cd8ae..00000000000
--- a/changelogs/unreleased/40301-rebase.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow user to rebase merge requests.
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/40418-migrate-existing-data-from-kubernetesservice-to-clusters-platforms-kubernetes.yml b/changelogs/unreleased/40418-migrate-existing-data-from-kubernetesservice-to-clusters-platforms-kubernetes.yml
deleted file mode 100644
index 5e158d831a6..00000000000
--- a/changelogs/unreleased/40418-migrate-existing-data-from-kubernetesservice-to-clusters-platforms-kubernetes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Migrate existing data from KubernetesService to Clusters::Platforms::Kubernetes
-merge_request: 15589
-author:
-type: changed
diff --git a/changelogs/unreleased/40453-fix-api-endpoints-to-edit-wiki-pages-where-project-belongs-to-a-group.yml b/changelogs/unreleased/40453-fix-api-endpoints-to-edit-wiki-pages-where-project-belongs-to-a-group.yml
deleted file mode 100644
index 30917098a95..00000000000
--- a/changelogs/unreleased/40453-fix-api-endpoints-to-edit-wiki-pages-where-project-belongs-to-a-group.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix API endpoints to edit wiki pages where project belongs to a group
-merge_request: 16170
-author:
-type: fixed
diff --git a/changelogs/unreleased/40492-update-admin-dashboard-content-order.yml b/changelogs/unreleased/40492-update-admin-dashboard-content-order.yml
new file mode 100644
index 00000000000..2416b15b6d5
--- /dev/null
+++ b/changelogs/unreleased/40492-update-admin-dashboard-content-order.yml
@@ -0,0 +1,5 @@
+---
+title: Move row containing Projects, Users and Groups count to the top in admin dashboard
+merge_request: 16421
+author:
+type: changed
diff --git a/changelogs/unreleased/40509_sorting_tags_api.yml b/changelogs/unreleased/40509_sorting_tags_api.yml
deleted file mode 100644
index 38b198d0fe3..00000000000
--- a/changelogs/unreleased/40509_sorting_tags_api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: add support for sorting in tags api
-merge_request: 15772
-author: haseebeqx
-type: added
diff --git a/changelogs/unreleased/40533-groups-tree-updates.yml b/changelogs/unreleased/40533-groups-tree-updates.yml
deleted file mode 100644
index 1bc0aa90f9e..00000000000
--- a/changelogs/unreleased/40533-groups-tree-updates.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Update groups tree to use GitLab SVG icons, add last updated at information
- for projects
-merge_request: 15980
-author:
-type: changed
diff --git a/changelogs/unreleased/40549-render-emoj-in-groups-overview.yml b/changelogs/unreleased/40549-render-emoj-in-groups-overview.yml
deleted file mode 100644
index 9b2f58df440..00000000000
--- a/changelogs/unreleased/40549-render-emoj-in-groups-overview.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rendering of emoji's in Group-Overview
-merge_request: 16098
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/40622-use-left-right-and-max-count.yml b/changelogs/unreleased/40622-use-left-right-and-max-count.yml
deleted file mode 100644
index c4c8f271cbe..00000000000
--- a/changelogs/unreleased/40622-use-left-right-and-max-count.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Improve the performance for counting diverging commits. Show 999+
- if it is more than 1000 commits
-merge_request: 15963
-author:
-type: performance
diff --git a/changelogs/unreleased/40780-choose-file.yml b/changelogs/unreleased/40780-choose-file.yml
deleted file mode 100644
index 73e59dfcce8..00000000000
--- a/changelogs/unreleased/40780-choose-file.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update Browse file to Choose file in all occurences
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/40818-last-push-widget-does-not-appear-after-pushing-new-commit.yml b/changelogs/unreleased/40818-last-push-widget-does-not-appear-after-pushing-new-commit.yml
new file mode 100644
index 00000000000..c57caf31d10
--- /dev/null
+++ b/changelogs/unreleased/40818-last-push-widget-does-not-appear-after-pushing-new-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Last push widget will show banner for new pushes to previously merged branch
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/40871-todo-notification-count-shows-notification-without-having-a-todo.yml b/changelogs/unreleased/40871-todo-notification-count-shows-notification-without-having-a-todo.yml
deleted file mode 100644
index ee196629def..00000000000
--- a/changelogs/unreleased/40871-todo-notification-count-shows-notification-without-having-a-todo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Reset todo counters when the target is deleted
-merge_request: 15807
-author:
-type: fixed
diff --git a/changelogs/unreleased/40895-fix-frequent-projects-stale-path.yml b/changelogs/unreleased/40895-fix-frequent-projects-stale-path.yml
deleted file mode 100644
index 485133b46a7..00000000000
--- a/changelogs/unreleased/40895-fix-frequent-projects-stale-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use relative URL for projects to avoid storing domains
-merge_request: 15876
-author:
-type: fixed
diff --git a/changelogs/unreleased/41016-import-gitlab-shell-projects.yml b/changelogs/unreleased/41016-import-gitlab-shell-projects.yml
deleted file mode 100644
index 47a9e9c3eec..00000000000
--- a/changelogs/unreleased/41016-import-gitlab-shell-projects.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Import some code and functionality from gitlab-shell to improve subprocess
- handling
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/41053-extend-cluster-applications-to-allow-install-to-prometheus.yml b/changelogs/unreleased/41053-extend-cluster-applications-to-allow-install-to-prometheus.yml
deleted file mode 100644
index ffb79d7d79f..00000000000
--- a/changelogs/unreleased/41053-extend-cluster-applications-to-allow-install-to-prometheus.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Prometheus to available Cluster applications
-merge_request: 15895
-author:
-type: added
diff --git a/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml b/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml
deleted file mode 100644
index b960b14624c..00000000000
--- a/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Disable creation of new Kubernetes Integrations unless they're active or created
- from template
-merge_request: 41054
-author:
-type: added
diff --git a/changelogs/unreleased/41056-create-cluster-from-kubernetes-integration-application-template.yml b/changelogs/unreleased/41056-create-cluster-from-kubernetes-integration-application-template.yml
deleted file mode 100644
index 2dd6fc5f1b5..00000000000
--- a/changelogs/unreleased/41056-create-cluster-from-kubernetes-integration-application-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow automatic creation of Kubernetes Integration from template
-merge_request: 16104
-author:
-type: added
diff --git a/changelogs/unreleased/41118-add-sorting-to-deployments-api.yml b/changelogs/unreleased/41118-add-sorting-to-deployments-api.yml
new file mode 100644
index 00000000000..a08f75f9fb9
--- /dev/null
+++ b/changelogs/unreleased/41118-add-sorting-to-deployments-api.yml
@@ -0,0 +1,5 @@
+---
+title: Adds sorting to deployments API
+merge_request: !16396
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/41163-improve-cluster-ingress-extra-cost-language.yml b/changelogs/unreleased/41163-improve-cluster-ingress-extra-cost-language.yml
new file mode 100644
index 00000000000..9c48831855c
--- /dev/null
+++ b/changelogs/unreleased/41163-improve-cluster-ingress-extra-cost-language.yml
@@ -0,0 +1,5 @@
+---
+title: Improve wording about additional costs for Ingress on custom clusters
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/41206-show-signin-pane-after-email-confirmation.yml b/changelogs/unreleased/41206-show-signin-pane-after-email-confirmation.yml
new file mode 100644
index 00000000000..5e706740962
--- /dev/null
+++ b/changelogs/unreleased/41206-show-signin-pane-after-email-confirmation.yml
@@ -0,0 +1,5 @@
+---
+title: Shows signin tab after new user email confirmation
+merge_request: 16174
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/41208-commit-atom-feeds-double-escaped.yml b/changelogs/unreleased/41208-commit-atom-feeds-double-escaped.yml
new file mode 100644
index 00000000000..76d3c6eda24
--- /dev/null
+++ b/changelogs/unreleased/41208-commit-atom-feeds-double-escaped.yml
@@ -0,0 +1,5 @@
+---
+title: Allows html text in commits atom feed
+merge_request: 16603
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/41244-issue-board-shortcut-working-while-no-issues.yml b/changelogs/unreleased/41244-issue-board-shortcut-working-while-no-issues.yml
deleted file mode 100644
index b2c3a86551b..00000000000
--- a/changelogs/unreleased/41244-issue-board-shortcut-working-while-no-issues.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: disables shortcut to issue boards when issues are not enabled
-merge_request: 16020
-author: Christiaan Van den Poel
-type: fixed
diff --git a/changelogs/unreleased/41247-timestamp.yml b/changelogs/unreleased/41247-timestamp.yml
new file mode 100644
index 00000000000..65f1a7485ad
--- /dev/null
+++ b/changelogs/unreleased/41247-timestamp.yml
@@ -0,0 +1,6 @@
+---
+title: For issues display time of last edit of title or description instead of time
+ of any attribute change
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/41249-clearing-the-cache.yml b/changelogs/unreleased/41249-clearing-the-cache.yml
deleted file mode 100644
index 221589a1239..00000000000
--- a/changelogs/unreleased/41249-clearing-the-cache.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Implement project jobs cache reset
-merge_request: 16067
-author:
-type: added
diff --git a/changelogs/unreleased/41268-bump-ruby-to-2-3-6.yml b/changelogs/unreleased/41268-bump-ruby-to-2-3-6.yml
deleted file mode 100644
index 188a854ebee..00000000000
--- a/changelogs/unreleased/41268-bump-ruby-to-2-3-6.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade Ruby to 2.3.6 to include security patches
-merge_request: 16016
-author:
-type: security
diff --git a/changelogs/unreleased/41424-gitlab-rake-gitlab-import-repos-schedules-an-import.yml b/changelogs/unreleased/41424-gitlab-rake-gitlab-import-repos-schedules-an-import.yml
deleted file mode 100644
index b495754a5a8..00000000000
--- a/changelogs/unreleased/41424-gitlab-rake-gitlab-import-repos-schedules-an-import.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix gitlab-rake gitlab:import:repos import schedule
-merge_request: 16115
-author:
-type: fixed
diff --git a/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml b/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml
deleted file mode 100644
index f69116382f0..00000000000
--- a/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix viewing merge request diffs where the underlying blobs are unavailable
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml b/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml
new file mode 100644
index 00000000000..bb5c1fdf082
--- /dev/null
+++ b/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml
@@ -0,0 +1,5 @@
+---
+title: Enables Project Milestone Deletion via the API
+merge_request: 16478
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/41491-fix-nil-blob-name-error.yml b/changelogs/unreleased/41491-fix-nil-blob-name-error.yml
deleted file mode 100644
index cf7e63ea46a..00000000000
--- a/changelogs/unreleased/41491-fix-nil-blob-name-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 500 error when visiting a commit where the blobs do not exist
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/41532-email-reason.yml b/changelogs/unreleased/41532-email-reason.yml
new file mode 100644
index 00000000000..83c28769217
--- /dev/null
+++ b/changelogs/unreleased/41532-email-reason.yml
@@ -0,0 +1,5 @@
+---
+title: Initial work to add notification reason to emails
+merge_request: 16160
+author: Mario de la Ossa
+type: added
diff --git a/changelogs/unreleased/41546-count-query-for-issues-and-mrs-runs-twice-on-group-index.yml b/changelogs/unreleased/41546-count-query-for-issues-and-mrs-runs-twice-on-group-index.yml
new file mode 100644
index 00000000000..7e42dc20ae8
--- /dev/null
+++ b/changelogs/unreleased/41546-count-query-for-issues-and-mrs-runs-twice-on-group-index.yml
@@ -0,0 +1,5 @@
+---
+title: Fix double query execution on groups page
+merge_request: 16314
+author:
+type: performance
diff --git a/changelogs/unreleased/41666-cannot-search-with-keyword-merge-2.yml b/changelogs/unreleased/41666-cannot-search-with-keyword-merge-2.yml
new file mode 100644
index 00000000000..48893862071
--- /dev/null
+++ b/changelogs/unreleased/41666-cannot-search-with-keyword-merge-2.yml
@@ -0,0 +1,5 @@
+---
+title: Only highlight search results under the highlighting size limit
+merge_request: 16462
+author:
+type: performance
diff --git a/changelogs/unreleased/41666-cannot-search-with-keyword-merge.yml b/changelogs/unreleased/41666-cannot-search-with-keyword-merge.yml
new file mode 100644
index 00000000000..3a6fa425c9c
--- /dev/null
+++ b/changelogs/unreleased/41666-cannot-search-with-keyword-merge.yml
@@ -0,0 +1,6 @@
+---
+title: Fix file search results when they match file contents with a number between
+ two colons
+merge_request: 16462
+author:
+type: fixed
diff --git a/changelogs/unreleased/41673-blank-query-members-api.yml b/changelogs/unreleased/41673-blank-query-members-api.yml
new file mode 100644
index 00000000000..677c5e250c8
--- /dev/null
+++ b/changelogs/unreleased/41673-blank-query-members-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error on empty query for Members API
+merge_request: 16235
+author:
+type: fixed
diff --git a/changelogs/unreleased/41731-predicate-memoization.yml b/changelogs/unreleased/41731-predicate-memoization.yml
new file mode 100644
index 00000000000..110f78063f4
--- /dev/null
+++ b/changelogs/unreleased/41731-predicate-memoization.yml
@@ -0,0 +1,5 @@
+---
+title: Properly memoize some predicate methods
+merge_request: 16329
+author:
+type: performance
diff --git a/changelogs/unreleased/41743-unused-selectors-for-cycle-analytics.yml b/changelogs/unreleased/41743-unused-selectors-for-cycle-analytics.yml
new file mode 100644
index 00000000000..03060c357fe
--- /dev/null
+++ b/changelogs/unreleased/41743-unused-selectors-for-cycle-analytics.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unused CSS selectors for Cycle Analytics
+merge_request: 16270
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/41749-postgres-9-6-for-ci-tests.yml b/changelogs/unreleased/41749-postgres-9-6-for-ci-tests.yml
new file mode 100644
index 00000000000..2a3d00f8e5f
--- /dev/null
+++ b/changelogs/unreleased/41749-postgres-9-6-for-ci-tests.yml
@@ -0,0 +1,5 @@
+---
+title: Add reason to keep postgresql 9.2 for CI
+merge_request: 16277
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/41754-update-scss-lint-to-0-56-0.yml b/changelogs/unreleased/41754-update-scss-lint-to-0-56-0.yml
deleted file mode 100644
index b96dd376cec..00000000000
--- a/changelogs/unreleased/41754-update-scss-lint-to-0-56-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update scss-lint to 0.56.0
-merge_request: 16278
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml b/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml
deleted file mode 100644
index fe87cd5cadb..00000000000
--- a/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix web ide user preferences copy and buttons
-merge_request: 41789
-author:
-type: other
diff --git a/changelogs/unreleased/41807-15665-consistently-502s-because-it-fetches-every-commit.yml b/changelogs/unreleased/41807-15665-consistently-502s-because-it-fetches-every-commit.yml
new file mode 100644
index 00000000000..146ae12afbd
--- /dev/null
+++ b/changelogs/unreleased/41807-15665-consistently-502s-because-it-fetches-every-commit.yml
@@ -0,0 +1,6 @@
+---
+title: Speed up loading merged merge requests when they contained a lot of commits
+ before merging
+merge_request: 16320
+author:
+type: performance
diff --git a/changelogs/unreleased/41814-text-decoration-skip.yml b/changelogs/unreleased/41814-text-decoration-skip.yml
new file mode 100644
index 00000000000..3e39d26be93
--- /dev/null
+++ b/changelogs/unreleased/41814-text-decoration-skip.yml
@@ -0,0 +1,5 @@
+---
+title: Improve readability of underlined links for dyslexic users
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/41956-fix-ctrl-enter-binding-to-save-comment.yml b/changelogs/unreleased/41956-fix-ctrl-enter-binding-to-save-comment.yml
new file mode 100644
index 00000000000..32a6f87d98e
--- /dev/null
+++ b/changelogs/unreleased/41956-fix-ctrl-enter-binding-to-save-comment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Ctrl+Enter keyboard shortcut saving comment/note edit
+merge_request: 16415
+author:
+type: fixed
diff --git a/changelogs/unreleased/42047-pg-10-support.yml b/changelogs/unreleased/42047-pg-10-support.yml
new file mode 100644
index 00000000000..f98e59329c3
--- /dev/null
+++ b/changelogs/unreleased/42047-pg-10-support.yml
@@ -0,0 +1,5 @@
+---
+title: Support PostgreSQL 10
+merge_request: 16471
+author:
+type: added
diff --git a/changelogs/unreleased/42055-update-marked-from-0-3-6-to-0-3-12.yml b/changelogs/unreleased/42055-update-marked-from-0-3-6-to-0-3-12.yml
new file mode 100644
index 00000000000..2b043761856
--- /dev/null
+++ b/changelogs/unreleased/42055-update-marked-from-0-3-6-to-0-3-12.yml
@@ -0,0 +1,5 @@
+---
+title: Update marked from 0.3.6 to 0.3.12
+merge_request: 16480
+author: Takuya Noguchi
+type: security
diff --git a/changelogs/unreleased/42154-fix-artifact-size-calc.yml b/changelogs/unreleased/42154-fix-artifact-size-calc.yml
new file mode 100644
index 00000000000..3d6911abf09
--- /dev/null
+++ b/changelogs/unreleased/42154-fix-artifact-size-calc.yml
@@ -0,0 +1,5 @@
+---
+title: Fix a bug calculating artifact size for project statistics
+merge_request: 16539
+author:
+type: fixed
diff --git a/changelogs/unreleased/42157-41989-fix-duplicate-in-create-item-dropdown.yml b/changelogs/unreleased/42157-41989-fix-duplicate-in-create-item-dropdown.yml
new file mode 100644
index 00000000000..ac8e4b034b5
--- /dev/null
+++ b/changelogs/unreleased/42157-41989-fix-duplicate-in-create-item-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Fix duplicate item in protected branch/tag dropdown
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/42159-utf8-uploads.yml b/changelogs/unreleased/42159-utf8-uploads.yml
new file mode 100644
index 00000000000..f6eba8f28f5
--- /dev/null
+++ b/changelogs/unreleased/42159-utf8-uploads.yml
@@ -0,0 +1,5 @@
+---
+title: Correctly escape UTF-8 path elements for uploads
+merge_request: 16560
+author:
+type: fixed
diff --git a/changelogs/unreleased/42206-permit-password-for-git-param.yml b/changelogs/unreleased/42206-permit-password-for-git-param.yml
new file mode 100644
index 00000000000..563dd528ad5
--- /dev/null
+++ b/changelogs/unreleased/42206-permit-password-for-git-param.yml
@@ -0,0 +1,5 @@
+---
+title: Permits 'password_authentication_enabled_for_git' parameter for ApplicationSettingsController
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/42231-protected-branches-api-route-returns-404-for-branches-with-dots.yml b/changelogs/unreleased/42231-protected-branches-api-route-returns-404-for-branches-with-dots.yml
new file mode 100644
index 00000000000..fbc589ea53d
--- /dev/null
+++ b/changelogs/unreleased/42231-protected-branches-api-route-returns-404-for-branches-with-dots.yml
@@ -0,0 +1,5 @@
+---
+title: Fix protected branches API to accept name parameter with dot
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ac-autodevopfix-kubectl-version.yml b/changelogs/unreleased/ac-autodevopfix-kubectl-version.yml
deleted file mode 100644
index 0ceeb7ccee1..00000000000
--- a/changelogs/unreleased/ac-autodevopfix-kubectl-version.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Force Auto DevOps kubectl version to 1.8.6
-merge_request: 16218
-author:
-type: fixed
diff --git a/changelogs/unreleased/add-tcp-check-rake-task.yml b/changelogs/unreleased/add-tcp-check-rake-task.yml
deleted file mode 100644
index a7c04bd0d55..00000000000
--- a/changelogs/unreleased/add-tcp-check-rake-task.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a gitlab:tcp_check rake task
-merge_request: 15759
-author:
-type: added
diff --git a/changelogs/unreleased/anchor-issue-references.yml b/changelogs/unreleased/anchor-issue-references.yml
deleted file mode 100644
index 78896427417..00000000000
--- a/changelogs/unreleased/anchor-issue-references.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix false positive issue references in merge requests caused by header anchor
- links.
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/api-domains-expose-project_id.yml b/changelogs/unreleased/api-domains-expose-project_id.yml
deleted file mode 100644
index 22617ffe9b5..00000000000
--- a/changelogs/unreleased/api-domains-expose-project_id.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose project_id on /api/v4/pages/domains
-merge_request: 16200
-author: Luc Didry
-type: changed
diff --git a/changelogs/unreleased/bump_mysql_gem.yml b/changelogs/unreleased/bump_mysql_gem.yml
deleted file mode 100644
index 58166949d72..00000000000
--- a/changelogs/unreleased/bump_mysql_gem.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump mysql2 gem version from 0.4.5 to 0.4.10
-merge_request:
-author: asaparov
-type: other
diff --git a/changelogs/unreleased/bvl-fork-public-project-to-private-namespace.yml b/changelogs/unreleased/bvl-fork-public-project-to-private-namespace.yml
deleted file mode 100644
index b802625943d..00000000000
--- a/changelogs/unreleased/bvl-fork-public-project-to-private-namespace.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow forking a public project to a private group
-merge_request: 16050
-author:
-type: changed
diff --git a/changelogs/unreleased/bvl-parent-preloading.yml b/changelogs/unreleased/bvl-parent-preloading.yml
new file mode 100644
index 00000000000..97c7bbb2a2a
--- /dev/null
+++ b/changelogs/unreleased/bvl-parent-preloading.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issues when rendering groups and their children
+merge_request: 16584
+author:
+type: fixed
diff --git a/changelogs/unreleased/change-issues-closed-at-background-migration.yml b/changelogs/unreleased/change-issues-closed-at-background-migration.yml
deleted file mode 100644
index 1c81c6a889e..00000000000
--- a/changelogs/unreleased/change-issues-closed-at-background-migration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use a background migration for issues.closed_at
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/changes-dropdown-ellipsis.yml b/changelogs/unreleased/changes-dropdown-ellipsis.yml
deleted file mode 100644
index 7e3f378cc33..00000000000
--- a/changelogs/unreleased/changes-dropdown-ellipsis.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed chanages dropdown ellipsis positioning
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml b/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml
deleted file mode 100644
index a5f1a958fa8..00000000000
--- a/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Eager load event target authors whenever possible
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml b/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml
deleted file mode 100644
index 74a00d49ab3..00000000000
--- a/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Handle GitLab hashed storage repositories using the repo import task
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/delay-background-migrations.yml b/changelogs/unreleased/delay-background-migrations.yml
deleted file mode 100644
index aa12591e7d2..00000000000
--- a/changelogs/unreleased/delay-background-migrations.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Run background migrations with a minimum interval
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/display-mr-in-commit-page.yml b/changelogs/unreleased/display-mr-in-commit-page.yml
new file mode 100644
index 00000000000..a9224c00b66
--- /dev/null
+++ b/changelogs/unreleased/display-mr-in-commit-page.yml
@@ -0,0 +1,5 @@
+---
+title: Add link on commit page to merge request that introduced that commit
+merge_request: 13713
+author: Hiroyuki Sato
+type: added
diff --git a/changelogs/unreleased/dm-diff-note-for-line-performance.yml b/changelogs/unreleased/dm-diff-note-for-line-performance.yml
deleted file mode 100644
index cbc418ab103..00000000000
--- a/changelogs/unreleased/dm-diff-note-for-line-performance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve performance of MR discussions on large diffs
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/docs-add-why-do-i-get-signed-out-authentication-section.yml b/changelogs/unreleased/docs-add-why-do-i-get-signed-out-authentication-section.yml
deleted file mode 100644
index bc245880ed0..00000000000
--- a/changelogs/unreleased/docs-add-why-do-i-get-signed-out-authentication-section.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add docs for why you might be signed out when using the Remember me token
-merge_request: 15756
-author:
-type: other
diff --git a/changelogs/unreleased/feat-add-section-headers-to-plus-button-dropdown.yml b/changelogs/unreleased/feat-add-section-headers-to-plus-button-dropdown.yml
new file mode 100644
index 00000000000..3fce53bc941
--- /dev/null
+++ b/changelogs/unreleased/feat-add-section-headers-to-plus-button-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Add section headers to plus button dropdown
+merge_request: 16394
+author: George Tsiolis
+type: added
diff --git a/changelogs/unreleased/feature-39591-visibility-level.yml b/changelogs/unreleased/feature-39591-visibility-level.yml
new file mode 100644
index 00000000000..4bbc9bdbb2e
--- /dev/null
+++ b/changelogs/unreleased/feature-39591-visibility-level.yml
@@ -0,0 +1,5 @@
+---
+title: Open visibility level help in a new tab
+merge_request:
+author: Jussi Räsänen
+type: fixed
diff --git a/changelogs/unreleased/feature-40842-provide-oracles-webgate-cookies-to-jira-requests.yml b/changelogs/unreleased/feature-40842-provide-oracles-webgate-cookies-to-jira-requests.yml
deleted file mode 100644
index d5ff5bc4627..00000000000
--- a/changelogs/unreleased/feature-40842-provide-oracles-webgate-cookies-to-jira-requests.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Provide additional cookies to JIRA service requests to allow Oracle WebGates
- Basic Auth
-merge_request:
-author: Stanislaw Wozniak
-type: changed
diff --git a/changelogs/unreleased/feature-api_runners_online.yml b/changelogs/unreleased/feature-api_runners_online.yml
deleted file mode 100644
index 08f4dd16f28..00000000000
--- a/changelogs/unreleased/feature-api_runners_online.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add online and status attribute to runner api entity
-merge_request: 11750
-author:
-type: added
diff --git a/changelogs/unreleased/feature-merge-request-system-hook.yml b/changelogs/unreleased/feature-merge-request-system-hook.yml
new file mode 100644
index 00000000000..cfc4c4235d6
--- /dev/null
+++ b/changelogs/unreleased/feature-merge-request-system-hook.yml
@@ -0,0 +1,5 @@
+---
+title: System hooks for Merge Requests
+merge_request: 14387
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/file-content-large-screen-padding.yml b/changelogs/unreleased/file-content-large-screen-padding.yml
new file mode 100644
index 00000000000..5691cd09b1f
--- /dev/null
+++ b/changelogs/unreleased/file-content-large-screen-padding.yml
@@ -0,0 +1,5 @@
+---
+title: Double padding for file-content wiki class on larger screens
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-abuse-reports-link-url.yml b/changelogs/unreleased/fix-abuse-reports-link-url.yml
deleted file mode 100644
index 44c26f35984..00000000000
--- a/changelogs/unreleased/fix-abuse-reports-link-url.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix abuse reports link url in admin area navbar
-merge_request: 16068
-author: megos
-type: fixed
diff --git a/changelogs/unreleased/fix-activity-inline-event-line-height.yml b/changelogs/unreleased/fix-activity-inline-event-line-height.yml
deleted file mode 100644
index 85e69567499..00000000000
--- a/changelogs/unreleased/fix-activity-inline-event-line-height.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix activity inline event line height on mobile
-merge_request: 16121
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/fix-add-horizontal-scroll-to-wiki-tables.yml b/changelogs/unreleased/fix-add-horizontal-scroll-to-wiki-tables.yml
new file mode 100644
index 00000000000..d8e97b7ad04
--- /dev/null
+++ b/changelogs/unreleased/fix-add-horizontal-scroll-to-wiki-tables.yml
@@ -0,0 +1,5 @@
+---
+title: Add horizontal scroll to wiki tables
+merge_request: 16527
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/fix-create-mr-from-issue-with-template.yml b/changelogs/unreleased/fix-create-mr-from-issue-with-template.yml
deleted file mode 100644
index 8668aa18669..00000000000
--- a/changelogs/unreleased/fix-create-mr-from-issue-with-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Execute quick actions (if present) when creating MR from issue
-merge_request: 15810
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-docs-help-shortcut.yml b/changelogs/unreleased/fix-docs-help-shortcut.yml
deleted file mode 100644
index 8c172e44160..00000000000
--- a/changelogs/unreleased/fix-docs-help-shortcut.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix shortcut links on help page
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-gb-fix-import-export-restoring-associations.yml b/changelogs/unreleased/fix-gb-fix-import-export-restoring-associations.yml
deleted file mode 100644
index 58df0024d61..00000000000
--- a/changelogs/unreleased/fix-gb-fix-import-export-restoring-associations.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix missing references to pipeline objects when restoring project with import/export
- feature
-merge_request: 16221
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-gb-improve-manual-action-tooltips.yml b/changelogs/unreleased/fix-gb-improve-manual-action-tooltips.yml
new file mode 100644
index 00000000000..31b4734bc79
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-improve-manual-action-tooltips.yml
@@ -0,0 +1,5 @@
+---
+title: Fix tooltip displayed for running manual actions
+merge_request: 16489
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-last-push-event-widget-layout.yml b/changelogs/unreleased/fix-last-push-event-widget-layout.yml
deleted file mode 100644
index ba5b115ca19..00000000000
--- a/changelogs/unreleased/fix-last-push-event-widget-layout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Last push event widget width for fixed layout
-merge_request: 15862
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/fix-move-2fa-disable-button.yml b/changelogs/unreleased/fix-move-2fa-disable-button.yml
deleted file mode 100644
index bac98ad5148..00000000000
--- a/changelogs/unreleased/fix-move-2fa-disable-button.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move 2FA disable button
-merge_request: 16177
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/fix-onion-skin-reenter.yml b/changelogs/unreleased/fix-onion-skin-reenter.yml
deleted file mode 100644
index 66b12c037b0..00000000000
--- a/changelogs/unreleased/fix-onion-skin-reenter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix onion-skin re-entering state
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-profile-settings-content-width.yml b/changelogs/unreleased/fix-profile-settings-content-width.yml
deleted file mode 100644
index bf164dc587d..00000000000
--- a/changelogs/unreleased/fix-profile-settings-content-width.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust content width for User Settings, GPG Keys
-merge_request: 16093
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/fix-profile-settings-sidebar-heading.yml b/changelogs/unreleased/fix-profile-settings-sidebar-heading.yml
deleted file mode 100644
index 75e0ea5612f..00000000000
--- a/changelogs/unreleased/fix-profile-settings-sidebar-heading.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Keep typographic hierarchy in User Settings
-merge_request: 16090
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/16117-improve-search-for-issues.yml b/changelogs/unreleased/fix-redirect-routes-schema.yml
index 92d5820ddd2..ea2b916307a 100644
--- a/changelogs/unreleased/16117-improve-search-for-issues.yml
+++ b/changelogs/unreleased/fix-redirect-routes-schema.yml
@@ -1,5 +1,5 @@
---
-title: Improve search query for issues.
+title: rework indexes on redirect_routes
merge_request:
author:
type: performance
diff --git a/changelogs/unreleased/fix-remove-unnecessary-sidebar-element-alignment.yml b/changelogs/unreleased/fix-remove-unnecessary-sidebar-element-alignment.yml
deleted file mode 100644
index 24f6f62b934..00000000000
--- a/changelogs/unreleased/fix-remove-unnecessary-sidebar-element-alignment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove unnecessary sidebar element realignment
-merge_request: 16159
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/fix_build_count_in_pipeline_success_maild.yml b/changelogs/unreleased/fix_build_count_in_pipeline_success_maild.yml
deleted file mode 100644
index c39bba62271..00000000000
--- a/changelogs/unreleased/fix_build_count_in_pipeline_success_maild.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: fix build count in pipeline success mail
-merge_request: 15827
-author: Christiaan Van den Poel
-type: fixed
diff --git a/changelogs/unreleased/fix_gitlab-ce-41891.yml b/changelogs/unreleased/fix_gitlab-ce-41891.yml
new file mode 100644
index 00000000000..56bdc1a7c32
--- /dev/null
+++ b/changelogs/unreleased/fix_gitlab-ce-41891.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fix custom header logo design nitpick: Remove unneeded margin on empty logo text'
+merge_request: 16383
+author: Markus Doits
+type: fixed
diff --git a/changelogs/unreleased/fj-40053-error-500-members-list.yml b/changelogs/unreleased/fj-40053-error-500-members-list.yml
deleted file mode 100644
index 8c82950bd41..00000000000
--- a/changelogs/unreleased/fj-40053-error-500-members-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixing error 500 when member exist but not the user
-merge_request: 15970
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-40279-normalize-ldap-dn-api.yml b/changelogs/unreleased/fj-40279-normalize-ldap-dn-api.yml
deleted file mode 100644
index 3fd8b0eb988..00000000000
--- a/changelogs/unreleased/fj-40279-normalize-ldap-dn-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Normalizing Identity extern_uid when saving the record
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml b/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml
deleted file mode 100644
index e4b1343876a..00000000000
--- a/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixing bug when wiki last version
-merge_request: 16197
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml b/changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml
deleted file mode 100644
index dca4dec224c..00000000000
--- a/changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added option to disable commits stats in the commit endpoint
-merge_request: 16309
-author:
-type: added
diff --git a/changelogs/unreleased/gitaly-git-http-ssh.yml b/changelogs/unreleased/gitaly-git-http-ssh.yml
new file mode 100644
index 00000000000..98812e92e2a
--- /dev/null
+++ b/changelogs/unreleased/gitaly-git-http-ssh.yml
@@ -0,0 +1,6 @@
+---
+title: Default to Gitaly for 'git push' HTTP/SSH, and make Gitaly mandatory for SSH
+ pull
+merge_request: 16586
+author:
+type: other
diff --git a/changelogs/unreleased/index-namespaces-lower-name.yml b/changelogs/unreleased/index-namespaces-lower-name.yml
deleted file mode 100644
index ef08b6d6755..00000000000
--- a/changelogs/unreleased/index-namespaces-lower-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add index on namespaces lower(name) for UsersController#exists
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/issue-description-field-typo.yml b/changelogs/unreleased/issue-description-field-typo.yml
deleted file mode 100644
index 9c4c179876d..00000000000
--- a/changelogs/unreleased/issue-description-field-typo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed typo for issue description field declaration
-merge_request:
-author: Marcus Amargi
-type: fixed
diff --git a/changelogs/unreleased/issue_37143_2.yml b/changelogs/unreleased/issue_37143_2.yml
new file mode 100644
index 00000000000..38125f666b2
--- /dev/null
+++ b/changelogs/unreleased/issue_37143_2.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unecessary query from labels filter
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/issue_40500.yml b/changelogs/unreleased/issue_40500.yml
deleted file mode 100644
index 35e8938fdad..00000000000
--- a/changelogs/unreleased/issue_40500.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix timeout when filtering issues by label
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/issue_41460.yml b/changelogs/unreleased/issue_41460.yml
new file mode 100644
index 00000000000..24d3eae6bf8
--- /dev/null
+++ b/changelogs/unreleased/issue_41460.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error on changes tab when merge request cannot be created
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/issues-40986-get-participants-from-issues-mr-api.yml b/changelogs/unreleased/issues-40986-get-participants-from-issues-mr-api.yml
deleted file mode 100644
index 4cac87b0cdb..00000000000
--- a/changelogs/unreleased/issues-40986-get-participants-from-issues-mr-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: get participants from merge_requests & issues'
-merge_request: 16187
-author: Brent Greeff
-type: added
diff --git a/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml b/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml
deleted file mode 100644
index 4386c631f59..00000000000
--- a/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Backport fast database lookup of SSH authorized_keys from EE
-merge_request: 16014
-author:
-type: added
diff --git a/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml b/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml
deleted file mode 100644
index 778eaa84381..00000000000
--- a/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added option to user preferences to enable the multi file editor
-merge_request: 16056
-author:
-type: added
diff --git a/changelogs/unreleased/jivl-fix-import-project-url-bug.yml b/changelogs/unreleased/jivl-fix-import-project-url-bug.yml
deleted file mode 100644
index 0d97b9c9a53..00000000000
--- a/changelogs/unreleased/jivl-fix-import-project-url-bug.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix import project url not updating project name
-merge_request: 16120
-author:
-type: fixed
diff --git a/changelogs/unreleased/jramsay-4012-i18n-compare.yml b/changelogs/unreleased/jramsay-4012-i18n-compare.yml
deleted file mode 100644
index ff15724be39..00000000000
--- a/changelogs/unreleased/jramsay-4012-i18n-compare.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add i18n helpers to branch comparison view
-merge_request: 16031
-author: James Ramsay
-type: added
diff --git a/changelogs/unreleased/jramsay-41590-add-readme-case.yml b/changelogs/unreleased/jramsay-41590-add-readme-case.yml
deleted file mode 100644
index 37b2bd44e0e..00000000000
--- a/changelogs/unreleased/jramsay-41590-add-readme-case.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix inconsistent downcase of filenames in prefilled `Add` commit messages
-merge_request: 16232
-author: James Ramsay
-type: fixed
diff --git a/changelogs/unreleased/ldap_username_attributes.yml b/changelogs/unreleased/ldap_username_attributes.yml
deleted file mode 100644
index 89bbca58fc9..00000000000
--- a/changelogs/unreleased/ldap_username_attributes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Modify `LDAP::Person` to return username value based on attributes
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/lfs-badge.yml b/changelogs/unreleased/lfs-badge.yml
deleted file mode 100644
index e4ed4d6741f..00000000000
--- a/changelogs/unreleased/lfs-badge.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added badge to tree & blob views to indicate LFS tracked files
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/merge-request-target-branch-perf.yml b/changelogs/unreleased/merge-request-target-branch-perf.yml
new file mode 100644
index 00000000000..37e326bfde3
--- /dev/null
+++ b/changelogs/unreleased/merge-request-target-branch-perf.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of target branch dropdown
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/mk-delete-orphaned-routes-before-validation.yml b/changelogs/unreleased/mk-delete-orphaned-routes-before-validation.yml
new file mode 100644
index 00000000000..55ab318df7d
--- /dev/null
+++ b/changelogs/unreleased/mk-delete-orphaned-routes-before-validation.yml
@@ -0,0 +1,6 @@
+---
+title: Ensure that users can reclaim a namespace or project path that is blocked by
+ an orphaned route
+merge_request: 16242
+author:
+type: fixed
diff --git a/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml b/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml
deleted file mode 100644
index 37fdb1df6df..00000000000
--- a/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Prevent excessive DB load due to faulty DeleteConflictingRedirectRoutes background
- migration
-merge_request: 16205
-author:
-type: fixed
diff --git a/changelogs/unreleased/multiple-clusters-single-list.yml b/changelogs/unreleased/multiple-clusters-single-list.yml
deleted file mode 100644
index 55743f3c00e..00000000000
--- a/changelogs/unreleased/multiple-clusters-single-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Present multiple clusters in a single list instead of a tabbed view
-merge_request: 15669
-author:
-type: changed
diff --git a/changelogs/unreleased/optimize-issues-avoid-noop-empty-cache-updates2.yml b/changelogs/unreleased/optimize-issues-avoid-noop-empty-cache-updates2.yml
deleted file mode 100644
index e0c3136be69..00000000000
--- a/changelogs/unreleased/optimize-issues-avoid-noop-empty-cache-updates2.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Treat empty markdown and html strings as valid cached text, not missing cache
- that needs to be updated
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/osw-introduce-merge-request-statistics.yml b/changelogs/unreleased/osw-introduce-merge-request-statistics.yml
deleted file mode 100644
index fed7c2141fb..00000000000
--- a/changelogs/unreleased/osw-introduce-merge-request-statistics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache merged and closed events data in merge_request_metrics table
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/remove-incorrect-guidance.yml b/changelogs/unreleased/remove-incorrect-guidance.yml
deleted file mode 100644
index eeb5745698f..00000000000
--- a/changelogs/unreleased/remove-incorrect-guidance.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Removed incorrect guidance stating blocked users will be removed from groups
- and project as members
-merge_request: 15947
-author: CesarApodaca
-type: fixed
diff --git a/changelogs/unreleased/remove-links-mr-empty-state.yml b/changelogs/unreleased/remove-links-mr-empty-state.yml
deleted file mode 100644
index c666bc2c81d..00000000000
--- a/changelogs/unreleased/remove-links-mr-empty-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove related links in MR widget when empty state
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/remove-soft-removals.yml b/changelogs/unreleased/remove-soft-removals.yml
deleted file mode 100644
index aa53d33e502..00000000000
--- a/changelogs/unreleased/remove-soft-removals.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove soft removals related code
-merge_request: 15789
-author:
-type: changed
diff --git a/changelogs/unreleased/remove-tabindexes-from-tag-form.yml b/changelogs/unreleased/remove-tabindexes-from-tag-form.yml
deleted file mode 100644
index a15bf2a7a4f..00000000000
--- a/changelogs/unreleased/remove-tabindexes-from-tag-form.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: removed tabindexes from tag form
-merge_request:
-author: Marcus Amargi
-type: changed
diff --git a/changelogs/unreleased/sh-add-schedule-pipeline-run-now.yml b/changelogs/unreleased/sh-add-schedule-pipeline-run-now.yml
deleted file mode 100644
index 6d06f695f10..00000000000
--- a/changelogs/unreleased/sh-add-schedule-pipeline-run-now.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add button to run scheduled pipeline immediately
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/sh-catch-invalid-uri-markdown.yml b/changelogs/unreleased/sh-catch-invalid-uri-markdown.yml
deleted file mode 100644
index 9b0233fe988..00000000000
--- a/changelogs/unreleased/sh-catch-invalid-uri-markdown.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Gracefully handle garbled URIs in Markdown
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-bare-import-hooks.yml b/changelogs/unreleased/sh-fix-bare-import-hooks.yml
deleted file mode 100644
index deb6c62f738..00000000000
--- a/changelogs/unreleased/sh-fix-bare-import-hooks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix hooks not being set up properly for bare import Rake task
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-log-when-user-blocked.yml b/changelogs/unreleased/sh-log-when-user-blocked.yml
new file mode 100644
index 00000000000..9abf2017514
--- /dev/null
+++ b/changelogs/unreleased/sh-log-when-user-blocked.yml
@@ -0,0 +1,5 @@
+---
+title: Log and send a system hook if a blocked user attempts to login
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/sh-make-kib-human.yml b/changelogs/unreleased/sh-make-kib-human.yml
deleted file mode 100644
index c40bb34fa4a..00000000000
--- a/changelogs/unreleased/sh-make-kib-human.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Humanize the units of "Showing last X KiB of log" in job trace
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-optimize-commit-stats.yml b/changelogs/unreleased/sh-optimize-commit-stats.yml
deleted file mode 100644
index 8c1be1252fb..00000000000
--- a/changelogs/unreleased/sh-optimize-commit-stats.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Speed up generation of commit stats by using Rugged native methods
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-remove-shared-runners-and-more.yml b/changelogs/unreleased/sh-remove-shared-runners-and-more.yml
new file mode 100644
index 00000000000..cc079617883
--- /dev/null
+++ b/changelogs/unreleased/sh-remove-shared-runners-and-more.yml
@@ -0,0 +1,5 @@
+---
+title: Remove erroneous text in shared runners page that suggested more runners available
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-validate-path-project-import.yml b/changelogs/unreleased/sh-validate-path-project-import.yml
deleted file mode 100644
index acad66c0ab2..00000000000
--- a/changelogs/unreleased/sh-validate-path-project-import.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid leaving a push event empty if payload cannot be created
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/show-inline-edit-btn.yml b/changelogs/unreleased/show-inline-edit-btn.yml
deleted file mode 100644
index 8cfe9b7d75a..00000000000
--- a/changelogs/unreleased/show-inline-edit-btn.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move edit button to second row on issue page (and change it to a pencil icon)
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml b/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml
deleted file mode 100644
index c2ab34b20a5..00000000000
--- a/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: show None when issue is in closed list and no labels assigned
-merge_request: 15976
-author: Christiaan Van den Poel
-type: fixed
diff --git a/changelogs/unreleased/sophie-h-gitlab-ce-patch-15.yml b/changelogs/unreleased/sophie-h-gitlab-ce-patch-15.yml
deleted file mode 100644
index b5e3210c737..00000000000
--- a/changelogs/unreleased/sophie-h-gitlab-ce-patch-15.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide link to issues/MRs from labels list if issues/MRs are disabled.
-merge_request: 15863
-author: Sophie Herold
-type: fixed
diff --git a/changelogs/unreleased/tc-correct-email-in-reply-to.yml b/changelogs/unreleased/tc-correct-email-in-reply-to.yml
deleted file mode 100644
index 1c8043f6a5c..00000000000
--- a/changelogs/unreleased/tc-correct-email-in-reply-to.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make mail notifications of discussion notes In-Reply-To of each other
-merge_request: 14289
-author:
-type: changed
diff --git a/changelogs/unreleased/update-redis-rack.yml b/changelogs/unreleased/update-redis-rack.yml
deleted file mode 100644
index 6e2e6e203b8..00000000000
--- a/changelogs/unreleased/update-redis-rack.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update redis-rack to 2.0.4
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/winh-modal-target-id.yml b/changelogs/unreleased/winh-modal-target-id.yml
deleted file mode 100644
index f8d5b72be50..00000000000
--- a/changelogs/unreleased/winh-modal-target-id.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add id to modal.vue to support data-toggle="modal"
-merge_request: 16189
-author:
-type: other
diff --git a/changelogs/unreleased/winh-search-page-filters.yml b/changelogs/unreleased/winh-search-page-filters.yml
new file mode 100644
index 00000000000..90c5cd8d818
--- /dev/null
+++ b/changelogs/unreleased/winh-search-page-filters.yml
@@ -0,0 +1,5 @@
+---
+title: Filter groups and projects dropdowns of search page on backend
+merge_request: 16336
+author:
+type: fixed
diff --git a/changelogs/unreleased/winh-style-modals.yml b/changelogs/unreleased/winh-style-modals.yml
new file mode 100644
index 00000000000..b7d0293960d
--- /dev/null
+++ b/changelogs/unreleased/winh-style-modals.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust modal style to new design
+merge_request: 16310
+author:
+type: other
diff --git a/changelogs/unreleased/winh-translate-contributors-page-dates.yml b/changelogs/unreleased/winh-translate-contributors-page-dates.yml
deleted file mode 100644
index 74801bbd86e..00000000000
--- a/changelogs/unreleased/winh-translate-contributors-page-dates.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Translate date ranges on contributors page
-merge_request: 15846
-author:
-type: changed
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index 60df92a44fc..5cd30dcde2a 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -503,3 +503,16 @@
:versions:
- 1.0.9
:when: 2017-11-16 13:02:06.765282000 Z
+- - :license
+ - JSONStream
+ - MIT
+ - :who: Tim Zallmann
+ :why: https://github.com/dominictarr/JSONStream/blob/master/LICENSE.MIT
+ :versions: []
+ :when: 2018-01-17 22:46:12.367554000 Z
+- - :approve
+ - uws
+ - :who: Tim Zallmann
+ :why: zlib license + Development Lib + https://github.com/uNetworking/uWebSockets/blob/master/LICENSE
+ :versions: []
+ :when: 2018-01-17 23:46:12.367554000 Z
diff --git a/config/initializers/ar5_pg_10_support.rb b/config/initializers/ar5_pg_10_support.rb
new file mode 100644
index 00000000000..6fae770015c
--- /dev/null
+++ b/config/initializers/ar5_pg_10_support.rb
@@ -0,0 +1,57 @@
+raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5
+
+require 'active_record/connection_adapters/postgresql_adapter'
+require 'active_record/connection_adapters/postgresql/schema_statements'
+
+#
+# Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330
+#
+# Updates sequence logic to support PostgreSQL 10.
+#
+# rubocop:disable all
+module ActiveRecord
+ module ConnectionAdapters
+
+ # We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu
+ # to work. In ActiveRecord 4, it is protected.
+ # https://github.com/mbleigh/seed-fu/issues/123
+ class PostgreSQLAdapter
+ public :postgresql_version
+ end
+
+ module PostgreSQL
+ module SchemaStatements
+ # Resets the sequence of a table's primary key to the maximum value.
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
+ unless pk and sequence
+ default_pk, default_sequence = pk_and_sequence_for(table)
+
+ pk ||= default_pk
+ sequence ||= default_sequence
+ end
+
+ if @logger && pk && !sequence
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
+ end
+
+ if pk && sequence
+ quoted_sequence = quote_table_name(sequence)
+ max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}")
+ if max_pk.nil?
+ if postgresql_version >= 100000
+ minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass")
+ else
+ minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
+ end
+ end
+
+ select_value <<-end_sql, 'SCHEMA'
+ SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
+ end_sql
+ end
+ end
+ end
+ end
+ end
+end
+# rubocop:enable all
diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb
index 3d83fb92d56..ee034d21eae 100644
--- a/config/initializers/warden.rb
+++ b/config/initializers/warden.rb
@@ -2,4 +2,8 @@ Rails.application.configure do |config|
Warden::Manager.after_set_user do |user, auth, opts|
Gitlab::Auth::UniqueIpsLimiter.limit_user!(user)
end
+
+ Warden::Manager.before_failure do |env, opts|
+ Gitlab::Auth::BlockedUserTracker.log_if_user_blocked(env)
+ end
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index bdf4b199c0a..43ada9ba145 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -50,6 +50,7 @@ constraints(ProjectUrlConstrainer.new) do
post :revert
post :cherry_pick
get :diff_for_path
+ get :merge_requests
end
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 95fa79990e2..229db11acb2 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -66,7 +66,6 @@ var config = {
pipelines_times: './pipelines/pipelines_times.js',
profile: './profile/profile_bundle.js',
project_import_gl: './projects/project_import_gitlab_project.js',
- project_new: './projects/project_new.js',
prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches',
protected_tags: './protected_tags',
@@ -120,7 +119,12 @@ var config = {
{
test: /\_worker\.js$/,
use: [
- { loader: 'worker-loader' },
+ {
+ loader: 'worker-loader',
+ options: {
+ inline: true
+ }
+ },
{ loader: 'babel-loader' },
],
},
diff --git a/db/migrate/20170425112128_create_pipeline_schedules_table.rb b/db/migrate/20170425112128_create_pipeline_schedules_table.rb
index 57df47f5f42..4f9c56a1ad8 100644
--- a/db/migrate/20170425112128_create_pipeline_schedules_table.rb
+++ b/db/migrate/20170425112128_create_pipeline_schedules_table.rb
@@ -17,7 +17,7 @@ class CreatePipelineSchedulesTable < ActiveRecord::Migration
t.boolean :active, default: true
t.datetime :deleted_at
- t.timestamps
+ t.timestamps null: true
end
add_index(:ci_pipeline_schedules, :project_id)
diff --git a/db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb b/db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb
new file mode 100644
index 00000000000..1b360b231a8
--- /dev/null
+++ b/db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb
@@ -0,0 +1,17 @@
+# rubocop:disable RemoveIndex
+
+class AddIndexOnMergeRequestDiffCommitSha < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_request_diff_commits, :sha, length: Gitlab::Database.mysql? ? 20 : nil
+ end
+
+ def down
+ remove_index :merge_request_diff_commits, :sha if index_exists? :merge_request_diff_commits, :sha
+ end
+end
diff --git a/db/migrate/20171207185153_add_merge_request_state_index.rb b/db/migrate/20171207185153_add_merge_request_state_index.rb
new file mode 100644
index 00000000000..72f846c5c38
--- /dev/null
+++ b/db/migrate/20171207185153_add_merge_request_state_index.rb
@@ -0,0 +1,18 @@
+class AddMergeRequestStateIndex < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_requests, [:source_project_id, :source_branch],
+ where: "state = 'opened'",
+ name: 'index_merge_requests_on_source_project_and_branch_state_opened'
+ end
+
+ def down
+ remove_concurrent_index_by_name :merge_requests,
+ 'index_merge_requests_on_source_project_and_branch_state_opened'
+ end
+end
diff --git a/db/migrate/20171211145425_add_can_push_to_deploy_keys_projects.rb b/db/migrate/20171211145425_add_can_push_to_deploy_keys_projects.rb
new file mode 100644
index 00000000000..5dc723db9f9
--- /dev/null
+++ b/db/migrate/20171211145425_add_can_push_to_deploy_keys_projects.rb
@@ -0,0 +1,15 @@
+class AddCanPushToDeployKeysProjects < 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 :deploy_keys_projects, :can_push, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_column :deploy_keys_projects, :can_push
+ end
+end
diff --git a/db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb b/db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb
new file mode 100644
index 00000000000..680855af945
--- /dev/null
+++ b/db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb
@@ -0,0 +1,64 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class PopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+ DATABASE_NAME = Gitlab::Database.database_name
+
+ disable_ddl_transaction!
+
+ class DeploysKeyProject < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'deploy_keys_projects'
+ end
+
+ def up
+ DeploysKeyProject.each_batch(of: 10_000) do |batch|
+ start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
+
+ if Gitlab::Database.mysql?
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects, #{DATABASE_NAME}.keys
+ SET deploy_keys_projects.can_push = #{DATABASE_NAME}.keys.can_push
+ WHERE deploy_keys_projects.deploy_key_id = #{DATABASE_NAME}.keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ else
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects
+ SET can_push = keys.can_push
+ FROM keys
+ WHERE deploy_key_id = keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ end
+ end
+ end
+
+ def down
+ DeploysKeyProject.each_batch(of: 10_000) do |batch|
+ start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
+
+ if Gitlab::Database.mysql?
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects, #{DATABASE_NAME}.keys
+ SET #{DATABASE_NAME}.keys.can_push = deploy_keys_projects.can_push
+ WHERE deploy_keys_projects.deploy_key_id = #{DATABASE_NAME}.keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ else
+ execute <<-EOF.strip_heredoc
+ UPDATE keys
+ SET can_push = deploy_keys_projects.can_push
+ FROM deploy_keys_projects
+ WHERE deploy_keys_projects.deploy_key_id = keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ end
+ end
+ end
+end
diff --git a/db/migrate/20180113220114_rework_redirect_routes_indexes.rb b/db/migrate/20180113220114_rework_redirect_routes_indexes.rb
new file mode 100644
index 00000000000..ab9971be074
--- /dev/null
+++ b/db/migrate/20180113220114_rework_redirect_routes_indexes.rb
@@ -0,0 +1,68 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ReworkRedirectRoutesIndexes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME_UNIQUE = "index_redirect_routes_on_path_unique_text_pattern_ops"
+
+ INDEX_NAME_PERM = "index_redirect_routes_on_path_text_pattern_ops_where_permanent"
+ INDEX_NAME_TEMP = "index_redirect_routes_on_path_text_pattern_ops_where_temporary"
+
+ OLD_INDEX_NAME_PATH_TPOPS = "index_redirect_routes_on_path_text_pattern_ops"
+ 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
+
+ # 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_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);")
+
+ # 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:
+
+ # 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};"
+ end
+
+ def down
+ disable_statement_timeout
+
+ add_concurrent_index(:redirect_routes, :permanent)
+
+ 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("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
diff --git a/db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb b/db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb
new file mode 100644
index 00000000000..3a5850df3db
--- /dev/null
+++ b/db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb
@@ -0,0 +1,63 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class PostPopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ DATABASE_NAME = Gitlab::Database.database_name
+
+ disable_ddl_transaction!
+
+ class DeploysKeyProject < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'deploy_keys_projects'
+ end
+
+ def up
+ DeploysKeyProject.each_batch(of: 10_000) do |batch|
+ start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
+
+ if Gitlab::Database.mysql?
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects, #{DATABASE_NAME}.keys
+ SET deploy_keys_projects.can_push = #{DATABASE_NAME}.keys.can_push
+ WHERE deploy_keys_projects.deploy_key_id = #{DATABASE_NAME}.keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ else
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects
+ SET can_push = keys.can_push
+ FROM keys
+ WHERE deploy_key_id = keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ end
+ end
+ end
+
+ def down
+ DeploysKeyProject.each_batch(of: 10_000) do |batch|
+ start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
+
+ if Gitlab::Database.mysql?
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects, #{DATABASE_NAME}.keys
+ SET #{DATABASE_NAME}.keys.can_push = deploy_keys_projects.can_push
+ WHERE deploy_keys_projects.deploy_key_id = #{DATABASE_NAME}.keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ else
+ execute <<-EOF.strip_heredoc
+ UPDATE keys
+ SET can_push = deploy_keys_projects.can_push
+ FROM deploy_keys_projects
+ WHERE deploy_keys_projects.deploy_key_id = keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ end
+ end
+ end
+end
diff --git a/db/post_migrate/20171215121259_remove_can_push_from_keys.rb b/db/post_migrate/20171215121259_remove_can_push_from_keys.rb
new file mode 100644
index 00000000000..0599811d986
--- /dev/null
+++ b/db/post_migrate/20171215121259_remove_can_push_from_keys.rb
@@ -0,0 +1,17 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveCanPushFromKeys < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ disable_ddl_transaction!
+
+ def up
+ remove_column :keys, :can_push
+ end
+
+ def down
+ add_column_with_default :keys, :can_push, :boolean, default: false, allow_null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8a6db61250b..a0901833c3d 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: 20180105212544) do
+ActiveRecord::Schema.define(version: 20180113220114) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -626,6 +626,7 @@ ActiveRecord::Schema.define(version: 20180105212544) do
t.integer "project_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.boolean "can_push", default: false, null: false
end
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
@@ -896,7 +897,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
t.string "type"
t.string "fingerprint"
t.boolean "public", default: false, null: false
- t.boolean "can_push", default: false, null: false
t.datetime "last_used_at"
end
@@ -1013,6 +1013,7 @@ ActiveRecord::Schema.define(version: 20180105212544) do
end
add_index "merge_request_diff_commits", ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_commits_on_mr_diff_id_and_order", unique: true, using: :btree
+ add_index "merge_request_diff_commits", ["sha"], name: "index_merge_request_diff_commits_on_sha", using: :btree
create_table "merge_request_diff_files", id: false, force: :cascade do |t|
t.integer "merge_request_diff_id", null: false
@@ -1108,6 +1109,7 @@ ActiveRecord::Schema.define(version: 20180105212544) do
add_index "merge_requests", ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)", using: :btree
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
+ add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)", using: :btree
add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree
add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
@@ -1536,8 +1538,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
end
add_index "redirect_routes", ["path"], name: "index_redirect_routes_on_path", unique: true, using: :btree
- add_index "redirect_routes", ["path"], name: "index_redirect_routes_on_path_text_pattern_ops", using: :btree, opclasses: {"path"=>"varchar_pattern_ops"}
- add_index "redirect_routes", ["permanent"], name: "index_redirect_routes_on_permanent", using: :btree
add_index "redirect_routes", ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id", using: :btree
create_table "releases", force: :cascade do |t|
diff --git a/doc/README.md b/doc/README.md
index 11d52001440..330670587f5 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -10,9 +10,9 @@ platform for software development!
GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans:
-- **GitLab Community Edition (CE)** is an [opensource product](https://gitlab.com/gitlab-org/gitlab-ce/),
+- **GitLab Community Edition (CE)** is an [open source product](https://gitlab.com/gitlab-org/gitlab-ce/),
self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com.
-- **GitLab Enterprise Edition (EE)** is an [opencore product](https://gitlab.com/gitlab-org/gitlab-ee/),
+- **GitLab Enterprise Edition (EE)** is an [open-core product](https://gitlab.com/gitlab-org/gitlab-ee/),
self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)**, **GitLab Enterprise Edition Premium (EEP)**, and **GitLab Enterprise Edition Ultimate (EEU)**.
- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings).
@@ -148,8 +148,8 @@ Regular users don't have access to GitLab administration tools and settings.
## Contributor documentation
-GitLab Community Edition is [opensource](https://gitlab.com/gitlab-org/gitlab-ce/)
-and Enterprise Editions are [opencore](https://gitlab.com/gitlab-org/gitlab-ee/).
+GitLab Community Edition is [open source](https://gitlab.com/gitlab-org/gitlab-ce/)
+and Enterprise Editions are [open-core](https://gitlab.com/gitlab-org/gitlab-ee/).
Learn how to contribute to GitLab:
- [Development](development/README.md): All styleguides and explanations how to contribute.
diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md
index cb42b7743c5..664657650d4 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -144,7 +144,7 @@ Now that the Okta app is configured, it's time to enable it in GitLab.
1. [Reconfigure][reconf] or [restart] GitLab for Omnibus and installations
from source respectively for the changes to take effect.
-You might want to try this out on a incognito browser window.
+You might want to try this out on an incognito browser window.
## Configuring groups
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 57e54815b68..2441ff85783 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -483,7 +483,7 @@ You can use GitLab as an auth endpoint and use a non-bundled Container Registry.
1. A certificate keypair is required for GitLab and the Container Registry to
communicate securely. By default omnibus-gitlab will generate one keypair,
which is saved to `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key`.
- When using an non-bundled Container Registry, you will need to supply a
+ When using a non-bundled Container Registry, you will need to supply a
custom certificate key. To do that, add the following to
`/etc/gitlab/gitlab.rb`
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index 0e92f7c5a34..fd2677996b1 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -154,7 +154,7 @@ who will take all the decisions to restore the service availability by:
- Reconfigure the old **Master** and demote to **Slave** when it comes back online
You must have at least `3` Redis Sentinel servers, and they need to
-be each in a independent machine (that are believed to fail independently),
+be each in an independent machine (that are believed to fail independently),
ideally in different geographical areas.
You can configure them in the same machines where you've configured the other
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index 835ed8c8006..9d1589d84aa 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -1,5 +1,11 @@
# Fast lookup of authorized SSH keys in the database
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1631) in
+> [GitLab Enterprise Edition Standard](https://about.gitlab.com/gitlab-ee) 9.3.
+>
+> [Available in](https://gitlab.com/gitlab-org/gitlab-ee/issues/3953) GitLab
+> Community Edition 10.4.
+
Regular SSH operations become slow as the number of users grows because OpenSSH
searches for a key to authorize a user via a linear search. In the worst case,
such as when the user is not authorized to access GitLab, OpenSSH will scan the
@@ -30,7 +36,7 @@ Add the following to your `sshd_config` file. This is usuaully located at
Omnibus Docker:
```
-AuthorizedKeysCommand /opt/embedded/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k
+AuthorizedKeysCommand /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k
AuthorizedKeysCommandUser git
```
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index d6924741ee4..3f9542d6653 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -172,7 +172,7 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of an issue |
-| `award_id` | integer | yes | The ID of a award_emoji |
+| `award_id` | integer | yes | The ID of an award_emoji |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/344
@@ -197,7 +197,7 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of an note |
+| `note_id` | integer | yes | The ID of a note |
```bash
@@ -323,7 +323,7 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of a award_emoji |
+| `award_id` | integer | yes | The ID of an award_emoji |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/345
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 273d5a56b6f..698fa22a438 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -19,15 +19,13 @@ Example response:
{
"id": 1,
"title": "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- "can_push": false,
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2013-10-02T10:12:29Z"
},
{
"id": 3,
"title": "Another Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- "can_push": true,
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2013-10-02T11:12:29Z"
}
]
@@ -57,15 +55,15 @@ Example response:
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- "can_push": false,
- "created_at": "2013-10-02T10:12:29Z"
+ "created_at": "2013-10-02T10:12:29Z",
+ "can_push": false
},
{
"id": 3,
"title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- "can_push": false,
- "created_at": "2013-10-02T11:12:29Z"
+ "created_at": "2013-10-02T11:12:29Z",
+ "can_push": false
}
]
```
@@ -96,8 +94,8 @@ Example response:
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- "can_push": false,
- "created_at": "2013-10-02T10:12:29Z"
+ "created_at": "2013-10-02T10:12:29Z",
+ "can_push": false
}
```
@@ -135,6 +133,36 @@ Example response:
}
```
+## Update deploy key
+
+Updates a deploy key for a project.
+
+```
+PUT /projects/:id/deploy_keys/:key_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `title` | string | no | New deploy key's title |
+| `can_push` | boolean | no | Can deploy key push to the project's repository |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "New deploy key", "can_push": true}' "https://gitlab.example.com/api/v4/projects/5/deploy_keys/11"
+```
+
+Example response:
+
+```json
+{
+ "id": 11,
+ "title": "New deploy key",
+ "key": "ssh-rsa AAAA...",
+ "created_at": "2015-08-29T12:44:31.550Z",
+ "can_push": true
+}
+```
+
## Delete deploy key
Removes a deploy key from the project. If the deploy key is used only for this project, it will be deleted from the system.
diff --git a/doc/api/deployments.md b/doc/api/deployments.md
index ab9e63e01d3..fd11894ea8f 100644
--- a/doc/api/deployments.md
+++ b/doc/api/deployments.md
@@ -11,6 +11,8 @@ GET /projects/:id/deployments
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `order_by`| string | no | Return deployments ordered by `id` or `iid` or `created_at` or `ref` fields. Default is `id` |
+| `sort` | string | no | Return deployments sorted in `asc` or `desc` order. Default is `asc` |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/deployments"
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index a96fb3124fc..21d3ac73000 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -73,7 +73,7 @@ POST /groups/:id/milestones
Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
-- `title` (required) - The title of an milestone
+- `title` (required) - The title of a milestone
- `description` (optional) - The description of the milestone
- `due_date` (optional) - The due date of the milestone
- `start_date` (optional) - The start date of the milestone
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 22ccc6a46f3..2957a0a5f48 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -591,7 +591,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| `title` | string | no | Title of MR |
| `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. |
| `milestone_id` | integer | no | The ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
-| `labels` | string | no | Comma-separated label names for an merge request. Set to an empty string to unassign all labels. |
+| `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. |
| `description` | string | no | Description of MR |
| `state_event` | string | no | New state (close/reopen) |
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 84930f0bdc9..07e66f89443 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -70,7 +70,7 @@ POST /projects/:id/milestones
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
-- `title` (required) - The title of an milestone
+- `title` (required) - The title of a milestone
- `description` (optional) - The description of the milestone
- `due_date` (optional) - The due date of the milestone
- `start_date` (optional) - The start date of the milestone
@@ -93,6 +93,19 @@ Parameters:
- `start_date` (optional) - The start date of the milestone
- `state_event` (optional) - The state event of the milestone (close|activate)
+## Delete project milestone
+
+Only for user with developer access to the project.
+
+```
+DELETE /projects/:id/milestones/:milestone_id
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of the project's milestone
+
## Get all issues assigned to a single milestone
Gets all issues assigned to a single project milestone.
diff --git a/doc/api/notes.md b/doc/api/notes.md
index d02ef84d0bd..1b68bd99ce2 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -158,7 +158,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project snippet
-- `note_id` (required) - The ID of an snippet note
+- `note_id` (required) - The ID of a snippet note
```json
{
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index ad2521230e6..cc495c5d091 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -131,12 +131,13 @@ Available only for admins.
GET /projects/:id/snippets/:snippet_id/user_agent_detail
```
-| Attribute | Type | Required | Description |
-|-------------|---------|----------|--------------------------------------|
-| `id` | Integer | yes | The ID of a snippet |
+| Attribute | Type | Required | Description |
+|---------------|---------|----------|--------------------------------------|
+| `id` | Integer | yes | The ID of a project |
+| `snippet_id` | Integer | yes | The ID of a snippet |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/snippets/1/user_agent_detail
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/snippets/2/user_agent_detail
```
Example response:
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 5a403f7593a..46f5de5aa0e 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -982,7 +982,7 @@ Example response:
## Unarchive a project
Unarchives the project if the user is either admin or the project owner of this project. This action is
-idempotent, thus unarchiving an non-archived project will not change the project.
+idempotent, thus unarchiving a non-archived project will not change the project.
```
POST /projects/:id/unarchive
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index 9750475f0a6..dd424470b67 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -33,6 +33,7 @@ Example response:
"created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
+ "merge_requests_events": true,
"enable_ssl_verification":true
}
]
@@ -54,6 +55,7 @@ POST /hooks
| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
| `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 |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
Example request:
@@ -72,6 +74,7 @@ Example response:
"created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
+ "merge_requests_events": true,
"enable_ssl_verification":true
}
]
diff --git a/doc/api/users.md b/doc/api/users.md
index 478d747a50d..1da6fcf297d 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -415,6 +415,10 @@ GET /user
}
```
+## List user projects
+
+Please refer to the [List of user projects ](projects.md#list-user-projects).
+
## List SSH keys
Get a list of currently authenticated user's SSH keys.
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 8385ef936c6..9f85533ea94 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -1,93 +1,18 @@
-# Technical Articles
+---
+comments: false
+---
-[Technical Articles](../development/writing_documentation.md#technical-articles) are
+# Technical articles list (deprecated)
+
+[Technical articles](../development/writing_documentation.md#technical-articles) are
topic-related documentation, written with an user-friendly approach and language, aiming
to provide the community with guidance on specific processes to achieve certain objectives.
-They are written by members of the GitLab Team and by
-[Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/).
-
-Part of the articles listed below link to the [GitLab Blog](https://about.gitlab.com/blog/),
-where they were originally published.
-
-## Build, test, and deploy with GitLab CI/CD
-
-Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/README.md):
-
-| Article title | Category | Publishing date |
-| :------------ | :------: | --------------: |
-| [Autoscaling GitLab Runners on AWS](runner_autoscale_aws/index.md) | Admin guide | 2017-11-24 |
-| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 |
-| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017-07-11 |
-| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017-07-27 |
-| [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/) | Tutorial | 2016-12-14 |
-| [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/) | Tutorial | 2016-11-30 |
-| [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/) | Tutorial | 2016-10-12 |
-| [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/) | Tutorial | 2016-08-11 |
-| [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) | Technical overview | 2016-06-09 |
-| [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/) | Technical overview | 2016-05-23 |
-| [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/) | Technical overview | 2017-05-15 |
-| [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) | Tutorial | 2016-03-10 |
-
-## GitLab Pages
-
-Learn how to deploy a static website with [GitLab Pages](../user/project/pages/index.md#getting-started):
-
-| Article title | Category | Publishing date |
-| :------------ | :------: | --------------: |
-| **Series: GitLab Pages from A to Z:** |
-| [- Part 1: Static sites and GitLab Pages domains](../user/project/pages/getting_started_part_one.md)| User guide | 2017-02-22 |
-| [- Part 2: Quick start guide - Setting up GitLab Pages](../user/project/pages/getting_started_part_two.md)| User guide | 2017-02-22 |
-| [- Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](../user/project/pages/getting_started_part_three.md)| User guide | 2017-02-22 |
-| [- Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](../user/project/pages/getting_started_part_four.md)| User guide | 2017-02-22 |
-| [Setting up GitLab Pages with CloudFlare Certificates](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) | Tutorial | 2017-02-07 |
-| [Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) | Tutorial | 2016-12-07 |
-| [Publish Code Coverage Report with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/) | Tutorial | 2016-11-03 |
-| [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) | Tutorial | 2016-08-26 |
-| [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/) | Tutorial | 2016-08-19 |
-| **Series: Static Site Generator:** |
-| [- Part 1: Dynamic vs Static Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) | Tutorial | 2016-06-03 |
-| [- Part 2: Modern Static Site Generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) | Tutorial | 2016-06-10 |
-| [- Part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) | Tutorial | 2016-06-17 |
-| [Securing your GitLab Pages with TLS and Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) | Tutorial | 2016-04-11 |
-| [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) | Tutorial | 2016-04-07 |
-
-## Install and maintain GitLab
-
-[Admin](../README.md#administrator-documentation), [install](../install/README.md),
-upgrade, integrate, migrate to GitLab:
-
-| Article title | Category | Publishing date |
-| :------------ | :------: | --------------: |
-| [Video Tutorial: Idea to Production on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/) | Tutorial | 2017-01-23 |
-| [How to Setup a GitLab Instance on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) | Tutorial | 2016-07-13 |
-| [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Tutorial | 2016-04-27 |
-
-## Software development
-
-Explore the best of GitLab's software development's capabilities:
-
-| Article title | Category | Publishing date |
-| :------------ | :------: | --------------: |
-| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 |
-| [From 2/3 of the Self-Hosted Git Market, to the Next-Generation CI System, to Auto DevOps](https://about.gitlab.com/2017/06/29/whats-next-for-gitlab-ci/)| Concepts | 2017-06-29 |
-| [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/) | Concepts | 2017-05-22 |
-| [Demo: Auto-Deploy from GitLab to an OpenShift Container Cluster](https://about.gitlab.com/2017/05/16/devops-containers-gitlab-openshift/) | Technical overview | 2017-05-16 |
-| [Demo: GitLab Service Desk](https://about.gitlab.com/2017/05/09/demo-service-desk/) | Feature highlight | 2017-05-09 |
-| [Demo: Mapping Work Versus Time, With Burndown Charts](https://about.gitlab.com/2017/04/25/mapping-work-to-do-versus-time-with-burndown-charts/) | Feature highlight | 2017-04-25 |
-| [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/) | Feature highlight | 2017-04-18 |
-| [Demo: Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/) | Feature highlight | 2017-03-17 |
-| [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/) | Technical overview | 2016-11-14 |
-| [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) | Technical overview | 2016-10-25 |
-| [Trends in Version Control Land: Microservices](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/) | Concepts | 2016-08-16 |
-| [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) | Concepts | 2016-08-05 |
-| [Trends in Version Control Land: Innersourcing](https://about.gitlab.com/2016/07/07/trends-version-control-innersourcing/) | Concepts | 2016-07-07 |
-| [Tutorial: It's all connected in GitLab](https://about.gitlab.com/2016/03/08/gitlab-tutorial-its-all-connected/) | Technical overview | 2016-03-08 |
-
-## Technologies
+The list of technical articles was [deprecated](https://gitlab.com/gitlab-org/gitlab-ce/issues/41138) in favor of having them linked from their topic-related documentation:
-| Article title | Category | Publishing date |
-| :------------ | :------: | --------------: |
-| [Why we are not leaving the cloud](https://about.gitlab.com/2017/03/02/why-we-are-not-leaving-the-cloud/) | Concepts | 2017-03-02 |
-| [Why We Chose Vue.js](https://about.gitlab.com/2016/10/20/why-we-chose-vue/) | Concepts | 2016-10-20 |
-| [Markdown Kramdown Tips & Tricks](https://about.gitlab.com/2016/07/19/markdown-kramdown-tips-and-tricks/) | Technical overview | 2016-07-19 |
+- [Git](../topics/git/index.md)
+- [GitLab administrator](../administration/index.md)
+- [GitLab CI/CD](../ci/README.md)
+- [GitLab Pages](../user/project/pages/index.md)
+- [GitLab user](../user/index.md)
+- [Install GitLab](../install/README.md)
diff --git a/doc/articles/numerous_undo_possibilities_in_git/index.md b/doc/articles/numerous_undo_possibilities_in_git/index.md
index 895bbccec08..3f46ee9a5e6 100644
--- a/doc/articles/numerous_undo_possibilities_in_git/index.md
+++ b/doc/articles/numerous_undo_possibilities_in_git/index.md
@@ -1,497 +1 @@
-# Numerous undo possibilities in Git
-
-> **Article [Type](../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
-> **Level:** intermediary ||
-> **Author:** [Crt Mori](https://gitlab.com/Letme) ||
-> **Publication date:** 2017-08-17
-
-## Introduction
-
-In this tutorial, we will show you different ways of undoing your work in Git, for which
-we will assume you have a basic working knowledge of. Check GitLab's
-[Git documentation](../../topics/git/index.md#git-documentation) for reference.
-Also, we will only provide some general info of the commands, which is enough
-to get you started for the easy cases/examples, but for anything more advanced please refer to the [Git book](https://git-scm.com/book/en/v2).
-
-We will explain a few different techniques to undo your changes based on the stage
-of the change in your current development. Also, keep in mind that [nothing in
-Git is really deleted.][git-autoclean-ref]
-This means that until Git automatically cleans detached commits (which cannot be
-accessed by branch or tag) it will be possible to view them with `git reflog` command
-and access them with direct commit-id. Read more about _[redoing the undo](#redoing-the-undo)_ on the section below.
-
-This guide is organized depending on the [stage of development][git-basics]
-where you want to undo your changes from and if they were shared with other developers
-or not. Because Git is tracking changes a created or edited file is in the unstaged state
-(if created it is untracked by Git). After you add it to a repository (`git add`) you put
-a file into the **staged** state, which is then committed (`git commit`) to your
-local repository. After that, file can be shared with other developers (`git push`).
-Here's what we'll cover in this tutorial:
-
- - [Undo local changes](#undo-local-changes) which were not pushed to remote repository
-
- - Before you commit, in both unstaged and staged state
- - After you committed
-
- - Undo changes after they are pushed to remote repository
-
- - [Without history modification](#undo-remote-changes-without-changing-history) (preferred way)
- - [With history modification](#undo-remote-changes-with-modifying-history) (requires
- coordination with team and force pushes).
-
- - [Usecases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable)
- - [How to modify history](#how-modifying-history-is-done)
- - [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits)
-
-
-### Branching strategy
-
-[Git][git-official] is a de-centralized version control system, which means that beside regular
-versioning of the whole repository, it has possibilities to exchange changes
-with other repositories. To avoid chaos with
-[multiple sources of truth][git-distributed], various
-development workflows have to be followed, and it depends on your internal
-workflow how certain changes or commits can be undone or changed.
-[GitLab Flow][gitlab-flow] provides a good
-balance between developers clashing with each other while
-developing the same feature and cooperating seamlessly, but it does not enable
-joined development of the same feature by multiple developers by default.
-When multiple developers develop the same feature on the same branch, clashing
-with every synchronization is unavoidable, but a proper or chosen Git Workflow will
-prevent that anything is lost or out of sync when feature is complete. You can also
-read through this blog post on [Git Tips & Tricks][gitlab-git-tips-n-tricks]
-to learn how to easily **do** things in Git.
-
-
-## Undo local changes
-
-Until you push your changes to any remote repository, they will only affect you.
-That broadens your options on how to handle undoing them. Still, local changes
-can be on various stages and each stage has a different approach on how to tackle them.
-
-
-### Unstaged local changes (before you commit)
-
-When a change is made, but it is not added to the staged tree, Git itself
-proposes a solution to discard changes to certain file.
-
-Suppose you edited a file to change the content using your favorite editor:
-
-```shell
-vim <file>
-```
-
-Since you did not `git add <file>` to staging, it should be under unstaged files (or
-untracked if file was created). You can confirm that with:
-
-```shell
-$ git status
-On branch master
-Your branch is up-to-date with 'origin/master'.
-Changes not staged for commit:
- (use "git add <file>..." to update what will be committed)
- (use "git checkout -- <file>..." to discard changes in working directory)
-
- modified: <file>
-no changes added to commit (use "git add" and/or "git commit -a")
-```
-
-At this point there are 3 options to undo the local changes you have:
-
- - Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes)
-
- ```shell
- git stash
- ```
-
- - Discarding local changes (permanently) to a file
-
- ```shell
- git checkout -- <file>
- ```
-
- - Discard all local changes to all files permanently
-
- ```shell
- git reset --hard
- ```
-
-
-Before executing `git reset --hard`, keep in mind that there is also a way to
-just temporary store the changes without committing them using `git stash`.
-This command resets the changes to all files, but it also saves them in case
-you would like to apply them at some later time. You can read more about it in
-[section below](#quickly-save-local-changes).
-
-### Quickly save local changes
-
-You are working on a feature when a boss drops by with an urgent task. Since your
-feature is not complete, but you need to swap to another branch, you can use
-`git stash` to save what you had done, swap to another branch, commit, push,
-test, then get back to previous feature branch, do `git stash pop` and continue
-where you left.
-
-The example above shows that discarding all changes is not always a preferred option,
-but Git provides a way to save them for later, while resetting the repository to state without
-them. This is achieved by Git stashing command `git stash`, which in fact saves your
-current work and runs `git reset --hard`, but it also has various
-additional options like:
-
- - `git stash save`, which enables including temporary commit message, which will help you identify changes, among with other options
- - `git stash list`, which lists all previously stashed commits (yes, there can be more) that were not `pop`ed
- - `git stash pop`, which redoes previously stashed changes and removes them from stashed list
- - `git stash apply`, which redoes previously stashed changes, but keeps them on stashed list
-
-### Staged local changes (before you commit)
-
-Let's say you have added some files to staging, but you want to remove them from the
-current commit, yet you want to retain those changes - just move them outside
-of the staging tree. You also have an option to discard all changes with
-`git reset --hard` or think about `git stash` [as described earlier.](#quickly-save-local-changes)
-
-Lets start the example by editing a file, with your favorite editor, to change the
-content and add it to staging
-
-```
-vim <file>
-git add <file>
-```
-
-The file is now added to staging as confirmed by `git status` command:
-
-```shell
-$ git status
-On branch master
-Your branch is up-to-date with 'origin/master'.
-Changes to be committed:
- (use "git reset HEAD <file>..." to unstage)
-
- new file: <file>
-```
-
-Now you have 4 options to undo your changes:
-
- - Unstage the file to current commit (HEAD)
-
- ```shell
- git reset HEAD <file>
- ```
-
- - Unstage everything - retain changes
-
- ```shell
- git reset
- ```
-
- - Discard all local changes, but save them for [later](#quickly-save-local-changes)
-
- ```shell
- git stash
- ```
-
- - Discard everything permanently
-
- ```shell
- git reset --hard
- ```
-
-## Committed local changes
-
-Once you commit, your changes are recorded by the version control system.
-Because you haven't pushed to your remote repository yet, your changes are
-still not public (or shared with other developers). At this point, undoing
-things is a lot easier, we have quite some workaround options. Once you push
-your code, you'll have less options to troubleshoot your work.
-
-### Without modifying history
-
-Through the development process some of the previously committed changes do not
-fit anymore in the end solution, or are source of the bugs. Once you find the
-commit which triggered bug, or once you have a faulty commit, you can simply
-revert it with `git revert commit-id`. This command inverts (swaps) the additions and
-deletions in that commit, so that it does not modify history. Retaining history
-can be helpful in future to notice that some changes have been tried
-unsuccessfully in the past.
-
-In our example we will assume there are commits `A`,`B`,`C`,`D`,`E` committed in this order: `A-B-C-D-E`,
-and `B` is the commit you want to undo. There are many different ways to identify commit
-`B` as bad, one of them is to pass a range to `git bisect` command. The provided range includes
-last known good commit (we assume `A`) and first known bad commit (where bug was detected - we will assume `E`).
-
-```shell
-git bisect A..E
-```
-
-Bisect will provide us with commit-id of the middle commit to test, and then guide us
-through simple bisection process. You can read more about it [in official Git Tools][git-debug]
-In our example we will end up with commit `B`, that introduced bug/error. We have
-4 options on how to remove it (or part of it) from our repository.
-
-- Undo (swap additions and deletions) changes introduced by commit `B`.
-
- ```shell
- git revert commit-B-id
- ```
-
-- Undo changes on a single file or directory from commit `B`, but retain them in the staged state
-
- ```shell
- git checkout commit-B-id <file>
- ```
-
-- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state
-
- ```shell
- git reset commit-B-id <file>
- ```
-
- - There is one command we also must not forget: **creating a new branch**
- from the point where changes are not applicable or where the development has hit a
- dead end. For example you have done commits `A-B-C-D` on your feature-branch
- and then you figure `C` and `D` are wrong. At this point you either reset to `B`
- and do commit `F` (which will cause problems with pushing and if forced pushed also with other developers)
- since branch now looks `A-B-F`, which clashes with what other developers have locally (you will
- [change history](#with-history-modification)), or you simply checkout commit `B` create
- a new branch and do commit `F`. In the last case, everyone else can still do their work while you
- have your new way to get it right and merge it back in later. Alternatively, with GitLab,
- you can [cherry-pick](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
- that commit into a new merge request.
-
- ![Create a new branch to avoid clashing](img/branching.png)
-
- ```shell
- git checkout commit-B-id
- git checkout -b new-path-of-feature
- # Create <commit F>
- git commit -a
- ```
-
-### With history modification
-
-There is one command for history modification and that is `git rebase`. Command
-provides interactive mode (`-i` flag) which enables you to:
-
- - **reword** commit messages (there is also `git commit --amend` for editing
- last commit message)
- - **edit** the commit content (changes introduced by commit) and message
- - **squash** multiple commits into a single one, and have a custom or aggregated
- commit message
- - **drop** commits - simply delete them
- - and few more options
-
-Let us check few examples. Again there are commits `A-B-C-D` where you want to
-delete commit `B`.
-
-- Rebase the range from current commit D to A:
-
- ```shell
- git rebase -i A
- ```
-
-- Command opens your favorite editor where you write `drop` in front of commit
- `B`, but you leave default `pick` with all other commits. Save and exit the
- editor to perform a rebase. Remember: if you want to cancel delete whole
- file content before saving and exiting the editor
-
-In case you want to modify something introduced in commit `B`.
-
-- Rebase the range from current commit D to A:
-
- ```shell
- git rebase -i A
- ```
-
-- Command opens your favorite text editor where you write `edit` in front of commit
- `B`, but leave default `pick` with all other commits. Save and exit the editor to
- perform a rebase
-
-- Now do your edits and commit changes:
-
- ```shell
- git commit -a
- ```
-
-You can find some more examples in [below section where we explain how to modify
-history](#how-modifying-history-is-done)
-
-
-### Redoing the Undo
-
-Sometimes you realize that the changes you undid were useful and you want them
-back. Well because of first paragraph you are in luck. Command `git reflog`
-enables you to *recall* detached local commits by referencing or applying them
-via commit-id. Although, do not expect to see really old commits in reflog, because
-Git regularly [cleans the commits which are *unreachable* by branches or tags][git-autoclean-ref].
-
-To view repository history and to track older commits you can use below command:
-
-```shell
-$ git reflog show
-
-# Example output:
-b673187 HEAD@{4}: merge 6e43d5987921bde189640cc1e37661f7f75c9c0b: Merge made by the 'recursive' strategy.
-eb37e74 HEAD@{5}: rebase -i (finish): returning to refs/heads/master
-eb37e74 HEAD@{6}: rebase -i (pick): Commit C
-97436c6 HEAD@{7}: rebase -i (start): checkout 97436c6eec6396c63856c19b6a96372705b08b1b
-...
-88f1867 HEAD@{12}: commit: Commit D
-97436c6 HEAD@{13}: checkout: moving from 97436c6eec6396c63856c19b6a96372705b08b1b to test
-97436c6 HEAD@{14}: checkout: moving from master to 97436c6
-05cc326 HEAD@{15}: commit: Commit C
-6e43d59 HEAD@{16}: commit: Commit B
-```
-
-Output of command shows repository history. In first column there is commit-id,
-in following column, number next to `HEAD` indicates how many commits ago something
-was made, after that indicator of action that was made (commit, rebase, merge, ...)
-and then on end description of that action.
-
-## Undo remote changes without changing history
-
-This topic is roughly same as modifying committed local changes without modifying
-history. **It should be the preferred way of undoing changes on any remote repository
-or public branch.** Keep in mind that branching is the best solution when you want
-to retain the history of faulty development, yet start anew from certain point. Branching
-enables you to include the existing changes in new development (by merging) and
-it also provides a clear timeline and development structure.
-
-![Use revert to keep branch flowing](img/revert.png)
-
-If you want to revert changes introduced in certain `commit-id` you can simply
-revert that `commit-id` (swap additions and deletions) in newly created commit:
-You can do this with
-
-```shell
-git revert commit-id
-```
-
-or creating a new branch:
-
-```shell
-git checkout commit-id
-git checkout -b new-path-of-feature
-```
-
-## Undo remote changes with modifying history
-
-This is useful when you want to *hide* certain things - like secret keys,
-passwords, SSH keys, etc. It is and should not be used to hide mistakes, as
-it will make it harder to debug in case there are some other bugs. The main
-reason for this is that you loose the real development progress. **Also keep in
-mind that, even with modified history, commits are just detached and can still be
-accessed through commit-id** - at least until all repositories perform
-the cleanup of detached commits (happens automatically).
-
-![Modifying history causes problems on remote branch](img/rebase_reset.png)
-
-### Where modifying history is generally acceptable
-
-Modified history breaks the development chain of other developers, as changed
-history does not have matching commits'ids. For that reason it should not
-be used on any public branch or on branch that *might* be used by other
-developers. When contributing to big open source repositories (e.g. [GitLab CE][gitlab-ce]),
-it is acceptable to *squash* commits into a single one, to present
-a nicer history of your contribution.
-Keep in mind that this also removes the comments attached to certain commits
-in merge requests, so if you need to retain traceability in GitLab, then
-modifying history is not acceptable.
-A feature-branch of a merge request is a public branch and might be used by
-other developers, but project process and rules might allow or require
-you to use `git rebase` (command that changes history) to reduce number of
-displayed commits on target branch after reviews are done (for example
-GitLab). There is a `git merge --squash` command which does exactly that
-(squashes commits on feature-branch to a single commit on target branch
-at merge).
-
->**Note:**
-Never modify the commit history of `master` or shared branch
-
-### How modifying history is done
-
-After you know what you want to modify (how far in history or how which range of
-old commits), use `git rebase -i commit-id`. This command will then display all the commits from
-current version to chosen commit-id and allow modification, squashing, deletion
-of that commits.
-
-```shell
-$ git rebase -i commit1-id..commit3-id
-pick <commit1-id> <commit1-commit-message>
-pick <commit2-id> <commit2-commit-message>
-pick <commit3-id> <commit3-commit-message>
-
-# Rebase commit1-id..commit3-id onto <commit4-id> (3 command(s))
-#
-# Commands:
-# p, pick = use commit
-# r, reword = use commit, but edit the commit message
-# e, edit = use commit, but stop for amending
-# s, squash = use commit, but meld into previous commit
-# f, fixup = like "squash", but discard this commit's log message
-# x, exec = run command (the rest of the line) using shell
-# d, drop = remove commit
-#
-# These lines can be re-ordered; they are executed from top to bottom.
-#
-# If you remove a line here THAT COMMIT WILL BE LOST.
-#
-# However, if you remove everything, the rebase will be aborted.
-#
-# Note that empty commits are commented out
-```
-
->**Note:**
-It is important to notice that comment from the output clearly states that, if
-you decide to abort, then do not just close your editor (as that will in-fact
-modify history), but remove all uncommented lines and save.
-
-That is one of the reasons why `git rebase` should be used carefully on
-shared and remote branches. But don't worry, there will be nothing broken until
-you push back to the remote repository (so you can freely explore the
-different outcomes locally).
-
-```shell
-# Modify history from commit-id to HEAD (current commit)
-git rebase -i commit-id
-```
-
-### Deleting sensitive information from commits
-
-Git also enables you to delete sensitive information from your past commits and
-it does modify history in the progress. That is why we have included it in this
-section and not as a standalone topic. To do so, you should run the
-`git filter-branch`, which enables you to rewrite history with
-[certain filters][git-filters-manual].
-This command uses rebase to modify history and if you want to remove certain
-file from history altogether use:
-
-```shell
-git filter-branch --tree-filter 'rm filename' HEAD
-```
-
-Since `git filter-branch` command might be slow on big repositories, there are
-tools that can use some of Git specifics to enable faster execution of common
-tasks (which is exactly what removing sensitive information file is about).
-An alternative is [BFG Repo-cleaner][bfg-repo-cleaner]. Keep in mind that these
-tools are faster because they do not provide a same fully feature set as `git filter-branch`
-does, but focus on specific usecases.
-
-## Conclusion
-
-There are various options of undoing your work with any version control system, but
-because of de-centralized nature of Git, these options are multiplied (or limited)
-depending on the stage of your process. Git also enables rewriting history, but that
-should be avoided as it might cause problems when multiple developers are
-contributing to the same codebase.
-
-<!-- Identifiers, in alphabetical order -->
-
-[bfg-repo-cleaner]: https://rtyley.github.io/bfg-repo-cleaner/
-[git-autoclean-ref]: https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery
-[git-basics]: https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository
-[git-debug]: https://git-scm.com/book/en/v2/Git-Tools-Debugging-with-Git
-[git-distributed]: https://git-scm.com/about/distributed
-[git-filters-manual]: https://git-scm.com/docs/git-filter-branch#_options
-[git-official]: https://git-scm.com/
-[gitlab-ce]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
-[gitlab-flow]: https://about.gitlab.com/2014/09/29/gitlab-flow/
-[gitlab-git-tips-n-tricks]: https://about.gitlab.com/2016/12/08/git-tips-and-tricks/
+This document was moved to [another location](../../topics/git/numerous_undo_possibilities_in_git/index.md).
diff --git a/doc/articles/runner_autoscale_aws/index.md b/doc/articles/runner_autoscale_aws/index.md
index 9d4c4a57ce5..e2667aebc5f 100644
--- a/doc/articles/runner_autoscale_aws/index.md
+++ b/doc/articles/runner_autoscale_aws/index.md
@@ -1,410 +1 @@
----
-last_updated: 2017-11-24
----
-
-> **[Article Type](../../development/writing_documentation.html#types-of-technical-articles):** Admin guide ||
-> **Level:** intermediary ||
-> **Author:** [Achilleas Pipinellis](https://gitlab.com/axil) ||
-> **Publication date:** 2017/11/24
-
-# Autoscaling GitLab Runner on AWS
-
-One of the biggest advantages of GitLab Runner is its ability to automatically
-spin up and down VMs to make sure your builds get processed immediately. It's a
-great feature, and if used correctly, it can be extremely useful in situations
-where you don't use your Runners 24/7 and want to have a cost-effective and
-scalable solution.
-
-## Introduction
-
-In this tutorial, we'll explore how to properly configure a GitLab Runner in
-AWS that will serve as the bastion where it will spawn new Docker machines on
-demand.
-
-In addition, we'll make use of [Amazon's EC2 Spot instances][spot] which will
-greatly reduce the costs of the Runner instances while still using quite
-powerful autoscaling machines.
-
-## Prerequisites
-
-NOTE: **Note:**
-A familiarity with Amazon Web Services (AWS) is required as this is where most
-of the configuration will take place.
-
-Your GitLab instance is going to need to talk to the Runners over the network,
-and that is something you need think about when configuring any AWS security
-groups or when setting up your DNS configuration.
-
-For example, you can keep the EC2 resources segmented away from public traffic
-in a different VPC to better strengthen your network security. Your environment
-is likely different, so consider what works best for your situation.
-
-### AWS security groups
-
-Docker Machine will attempt to use a
-[default security group](https://docs.docker.com/machine/drivers/aws/#security-group)
-with rules for port `2376`, which is required for communication with the Docker
-daemon. Instead of relying on Docker, you can create a security group with the
-rules you need and provide that in the Runner options as we will
-[see below](#the-runners-machine-section). This way, you can customize it to your
-liking ahead of time based on your networking environment.
-
-### AWS credentials
-
-You'll need an [AWS Access Key](https://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html)
-tied to a user with permission to scale (EC2) and update the cache (via S3).
-Create a new user with [policies](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-policies-for-amazon-ec2.html)
-for EC2 (AmazonEC2FullAccess) and S3 (AmazonS3FullAccess). To be more secure,
-you can disable console login for that user. Keep the tab open or copy paste the
-security credentials in an editor as we'll use them later during the
-[Runner configuration](#the-runners-machine-section).
-
-## Prepare the bastion instance
-
-The first step is to install GitLab Runner in an EC2 instance that will serve
-as the bastion that spawns new machines. This doesn't have to be a powerful
-machine since it will not run any jobs itself, a `t2.micro` instance will do.
-This machine will be a dedicated host since we need it always up and running,
-thus it will be the only standard cost.
-
-NOTE: **Note:**
-For the bastion instance, choose a distribution that both Docker and GitLab
-Runner support, for example either Ubuntu, Debian, CentOS or RHEL will work fine.
-
-Install the prerequisites:
-
-1. Log in to your server
-1. [Install GitLab Runner from the official GitLab repository](https://docs.gitlab.com/runner/install/linux-repository.html)
-1. [Install Docker](https://docs.docker.com/engine/installation/#server)
-1. [Install Docker Machine](https://docs.docker.com/machine/install-machine/)
-
-Now that the Runner is installed, it's time to register it.
-
-## Registering the GitLab Runner
-
-Before configuring the GitLab Runner, you need to first register it, so that
-it connects with your GitLab instance:
-
-1. [Obtain a Runner token](../../ci/runners/README.md)
-1. [Register the Runner](https://docs.gitlab.com/runner/register/index.html#gnu-linux)
-1. When asked the executor type, enter `docker+machine`
-
-You can now move on to the most important part, configuring the GitLab Runner.
-
-TIP: **Tip:**
-If you want every user in your instance to be able to use the autoscaled Runners,
-register the Runner as a shared one.
-
-## Configuring the GitLab Runner
-
-Now that the Runner is registered, you need to edit its configuration file and
-add the required options for the AWS machine driver.
-
-Let's first break it down to pieces.
-
-### The global section
-
-In the global section, you can define the limit of the jobs that can be run
-concurrently across all Runners (`concurrent`). This heavily depends on your
-needs, like how many users your Runners will accommodate, how much time your
-builds take, etc. You can start with something low like `10`, and increase or
-decrease its value going forward.
-
-The `check_interval` option defines how often the Runner should check GitLab
-for new jobs, in seconds.
-
-Example:
-
-```toml
-concurrent = 10
-check_interval = 0
-```
-
-[Read more](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section)
-about all the options you can use.
-
-### The `runners` section
-
-From the `[[runners]]` section, the most important part is the `executor` which
-must be set to `docker+machine`. Most of those settings are taken care of when
-you register the Runner for the first time.
-
-`limit` sets the maximum number of machines (running and idle) that this Runner
-will spawn. For more info check the [relationship between `limit`, `concurrent`
-and `IdleCount`](https://docs.gitlab.com/runner/configuration/autoscale.html#how-concurrent-limit-and-idlecount-generate-the-upper-limit-of-running-machines).
-
-Example:
-
-```toml
-[[runners]]
- name = "gitlab-aws-autoscaler"
- url = "<URL of your GitLab instance>"
- token = "<Runner's token>"
- executor = "docker+machine"
- limit = 20
-```
-
-[Read more](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section)
-about all the options you can use under `[[runners]]`.
-
-### The `runners.docker` section
-
-In the `[runners.docker]` section you can define the default Docker image to
-be used by the child Runners if it's not defined in [`.gitlab-ci.yml`](../../ci/yaml/README.md).
-By using `privileged = true`, all Runners will be able to run
-[Docker in Docker](../../ci/docker/using_docker_build.md#use-docker-in-docker-executor)
-which is useful if you plan to build your own Docker images via GitLab CI/CD.
-
-Next, we use `disable_cache = true` to disable the Docker executor's inner
-cache mechanism since we will use the distributed cache mode as described
-in the following section.
-
-Example:
-
-```toml
- [runners.docker]
- image = "alpine"
- privileged = true
- disable_cache = true
-```
-
-[Read more](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-docker-section)
-about all the options you can use under `[runners.docker]`.
-
-### The `runners.cache` section
-
-To speed up your jobs, GitLab Runner provides a cache mechanism where selected
-directories and/or files are saved and shared between subsequent jobs.
-While not required for this setup, it is recommended to use the distributed cache
-mechanism that GitLab Runner provides. Since new instances will be created on
-demand, it is essential to have a common place where the cache is stored.
-
-In the following example, we use Amazon S3:
-
-```toml
- [runners.cache]
- Type = "s3"
- ServerAddress = "s3.amazonaws.com"
- AccessKey = "<your AWS Access Key ID>"
- SecretKey = "<your AWS Secret Access Key>"
- BucketName = "<the bucket where your cache should be kept>"
- BucketLocation = "us-east-1"
- Shared = true
-```
-
-Here's some more info to further explore the cache mechanism:
-
-- [Reference for `runners.cache`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-cache-section)
-- [Deploying and using a cache server for GitLab Runner](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching)
-- [How cache works](../../ci/yaml/README.md#cache)
-
-### The `runners.machine` section
-
-This is the most important part of the configuration and it's the one that
-tells GitLab Runner how and when to spawn new or remove old Docker Machine
-instances.
-
-We will focus on the AWS machine options, for the rest of the settings read
-about the:
-
-- [Autoscaling algorithm and the parameters it's based on](https://docs.gitlab.com/runner/configuration/autoscale.html#autoscaling-algorithm-and-parameters) - depends on the needs of your organization
-- [Off peak time configuration](https://docs.gitlab.com/runner/configuration/autoscale.html#off-peak-time-mode-configuration) - useful when there are regular time periods in your organization when no work is done, for example weekends
-
-Here's an example of the `runners.machine` section:
-
-```toml
- [runners.machine]
- IdleCount = 1
- IdleTime = 1800
- MaxBuilds = 10
- OffPeakPeriods = [
- "* * 0-9,18-23 * * mon-fri *",
- "* * * * * sat,sun *"
- ]
- OffPeakIdleCount = 0
- OffPeakIdleTime = 1200
- MachineDriver = "amazonec2"
- MachineName = "gitlab-docker-machine-%s"
- MachineOptions = [
- "amazonec2-access-key=XXXX",
- "amazonec2-secret-key=XXXX",
- "amazonec2-region=us-central-1",
- "amazonec2-vpc-id=vpc-xxxxx",
- "amazonec2-subnet-id=subnet-xxxxx",
- "amazonec2-use-private-address=true",
- "amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,true",
- "amazonec2-security-group=docker-machine-scaler",
- "amazonec2-instance-type=m4.2xlarge",
- ]
-```
-
-The Docker Machine driver is set to `amazonec2` and the machine name has a
-standard prefix followed by `%s` (required) that is replaced by the ID of the
-child Runner: `gitlab-docker-machine-%s`.
-
-Now, depending on your AWS infrastructure, there are many options you can set up
-under `MachineOptions`. Below you can see the most common ones.
-
-| Machine option | Description |
-| -------------- | ----------- |
-| `amazonec2-access-key=XXXX` | The AWS access key of the user that has permissions to create EC2 instances, see [AWS credentials](#aws-credentials). |
-| `amazonec2-secret-key=XXXX` | The AWS secret key of the user that has permissions to create EC2 instances, see [AWS credentials](#aws-credentials). |
-| `amazonec2-region=eu-central-1` | The region to use when launching the instance. You can omit this entirely and the default `us-east-1` will be used. |
-| `amazonec2-vpc-id=vpc-xxxxx` | Your [VPC ID](https://docs.docker.com/machine/drivers/aws/#vpc-id) to launch the instance in. |
-| `amazonec2-subnet-id=subnet-xxxx` | The AWS VPC subnet ID. |
-| `amazonec2-use-private-address=true` | Use the private IP address of Docker Machines, but still create a public IP address. Useful to keep the traffic internal and avoid extra costs.|
-| `amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,true` | AWS extra tag key-value pairs, useful to identify the instances on the AWS console. The "Name" tag is set to the machine name by default. We set the "runner-manager-name" to match the Runner name set in `[[runners]]`, so that we can filter all the EC2 instances created by a specific manager setup. Read more about [using tags in AWS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html). |
-| `amazonec2-security-group=docker-machine-scaler` | AWS VPC security group name, see [AWS security groups](#aws-security-groups). |
-| `amazonec2-instance-type=m4.2xlarge` | The instance type that the child Runners will run on. |
-
-TIP: **Tip:**
-Under `MachineOptions` you can add anything that the [AWS Docker Machine driver
-supports](https://docs.docker.com/machine/drivers/aws/#options). You are highly
-encouraged to read Docker's docs as your infrastructure setup may warrant
-different options to be applied.
-
-NOTE: **Note:**
-The child instances will use by default Ubuntu 16.04 unless you choose a
-different AMI ID by setting `amazonec2-ami`.
-
-NOTE: **Note:**
-If you specify `amazonec2-private-address-only=true` as one of the machine
-options, your EC2 instance won't get assigned a public IP. This is ok if your
-VPC is configured correctly with an Internet Gateway (IGW) and routing is fine,
-but it’s something to consider if you've got a more complex configuration. Read
-more in [Docker docs about VPC connectivity](https://docs.docker.com/machine/drivers/aws/#vpc-connectivity).
-
-[Read more](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-machine-section)
-about all the options you can use under `[runners.machine]`.
-
-### Getting it all together
-
-Here's the full example of `/etc/gitlab-runner/config.toml`:
-
-```toml
-concurrent = 10
-check_interval = 0
-
-[[runners]]
- name = "gitlab-aws-autoscaler"
- url = "<URL of your GitLab instance>"
- token = "<Runner's token>"
- executor = "docker+machine"
- limit = 20
- [runners.docker]
- image = "alpine"
- privileged = true
- disable_cache = true
- [runners.cache]
- Type = "s3"
- ServerAddress = "s3.amazonaws.com"
- AccessKey = "<your AWS Access Key ID>"
- SecretKey = "<your AWS Secret Access Key>"
- BucketName = "<the bucket where your cache should be kept>"
- BucketLocation = "us-east-1"
- Shared = true
- [runners.machine]
- IdleCount = 1
- IdleTime = 1800
- MaxBuilds = 100
- OffPeakPeriods = [
- "* * 0-9,18-23 * * mon-fri *",
- "* * * * * sat,sun *"
- ]
- OffPeakIdleCount = 0
- OffPeakIdleTime = 1200
- MachineDriver = "amazonec2"
- MachineName = "gitlab-docker-machine-%s"
- MachineOptions = [
- "amazonec2-access-key=XXXX",
- "amazonec2-secret-key=XXXX",
- "amazonec2-region=us-central-1",
- "amazonec2-vpc-id=vpc-xxxxx",
- "amazonec2-subnet-id=subnet-xxxxx",
- "amazonec2-use-private-address=true",
- "amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,true",
- "amazonec2-security-group=docker-machine-scaler",
- "amazonec2-instance-type=m4.2xlarge",
- ]
-```
-
-## Cutting down costs with Amazon EC2 Spot instances
-
-As [described by][spot] Amazon:
-
->
-Amazon EC2 Spot instances allow you to bid on spare Amazon EC2 computing capacity.
-Since Spot instances are often available at a discount compared to On-Demand
-pricing, you can significantly reduce the cost of running your applications,
-grow your application’s compute capacity and throughput for the same budget,
-and enable new types of cloud computing applications.
-
-In addition to the [`runners.machine`](#the-runners-machine-section) options
-you picked above, in `/etc/gitlab-runner/config.toml` under the `MachineOptions`
-section, add the following:
-
-```toml
- MachineOptions = [
- "amazonec2-request-spot-instance=true",
- "amazonec2-spot-price=0.03",
- "amazonec2-block-duration-minutes=60"
- ]
-```
-
-With this configuration, Docker Machines are created on Spot instances with a
-maximum bid price of $0.03 per hour and the duration of the Spot instance is
-capped at 60 minutes. The `0.03` number mentioned above is just an example, so
-be sure to check on the current pricing based on the region you picked.
-
-To learn more about Amazon EC2 Spot instances, visit the following links:
-
-- https://aws.amazon.com/ec2/spot/
-- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-requests.html
-- https://aws.amazon.com/blogs/aws/focusing-on-spot-instances-lets-talk-about-best-practices/
-
-### Caveats of Spot instances
-
-While Spot instances is a great way to use unused resources and minimize the
-costs of your infrastructure, you must be aware of the implications.
-
-Running CI jobs on Spot instances may increase the failure rates because of the
-Spot instances pricing model. If the price exceeds your bid, the existing Spot
-instances will be immediately terminated and all your jobs on that host will fail.
-
-As a consequence, the auto-scale Runner would fail to create new machines while
-it will continue to request new instances. This eventually will make 60 requests
-and then AWS won't accept any more. Then once the Spot price is acceptable, you
-are locked out for a bit because the call amount limit is exceeded.
-
-If you encounter that case, you can use the following command in the bastion
-machine to see the Docker Machines state:
-
-```sh
-docker-machine ls -q --filter state=Error --format "{{.NAME}}"
-```
-
-NOTE: **Note:**
-There are some issues regarding making GitLab Runner gracefully handle Spot
-price changes, and there are reports of `docker-machine` attempting to
-continually remove a Docker Machine. GitLab has provided patches for both cases
-in the upstream project. For more information, see issues
-[#2771](https://gitlab.com/gitlab-org/gitlab-runner/issues/2771) and
-[#2772](https://gitlab.com/gitlab-org/gitlab-runner/issues/2772).
-
-## Conclusion
-
-In this guide we learned how to install and configure a GitLab Runner in
-autoscale mode on AWS.
-
-Using the autoscale feature of GitLab Runner can save you both time and money.
-Using the Spot instances that AWS provides can save you even more, but you must
-be aware of the implications. As long as your bid is high enough, there shouldn't
-be an issue.
-
-You can read the following use cases from which this tutorial was (heavily)
-influenced:
-
-- [HumanGeo - Scaling GitLab CI](http://blog.thehumangeo.com/gitlab-autoscale-runners.html)
-- [subtrakt Health - Autoscale GitLab CI Runners and save 90% on EC2 costs](https://substrakthealth.com/news/gitlab-ci-cost-savings/)
-
-[spot]: https://aws.amazon.com/ec2/spot/
+This document was moved to [another location](https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/index.html).
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 5829aaee9c9..eabeb4510db 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -2,151 +2,120 @@
comments: false
---
-# GitLab Continuous Integration (GitLab CI)
+# GitLab Continuous Integration (GitLab CI/CD)
![Pipeline graph](img/cicd_pipeline_infograph.png)
The benefits of Continuous Integration are huge when automation plays an
integral part of your workflow. GitLab comes with built-in Continuous
-Integration, Continuous Deployment, and Continuous Delivery support to build,
-test, and deploy your application.
+Integration, Continuous Deployment, and Continuous Delivery support
+to build, test, and deploy your application.
Here's some info we've gathered to get you started.
## Getting started
-The first steps towards your GitLab CI journey.
+The first steps towards your GitLab CI/CD journey.
-- [Getting started with GitLab CI](quick_start/README.md)
-- [Pipelines and jobs](pipelines.md)
-- [Configure a Runner, the application that runs your jobs](runners/README.md)
-- **Articles:**
- - [Getting started with GitLab and GitLab CI - Intro to CI](https://about.gitlab.com/2015/12/14/getting-started-with-gitlab-and-gitlab-ci/)
- - [Continuous Integration, Delivery, and Deployment with GitLab - Intro to CI/CD](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
- - [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
- - [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
- - [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
+- [Getting started with GitLab CI/CD](quick_start/README.md): understand how GitLab CI/CD works.
+- GitLab CI/CD configuration file: [`.gitlab-ci.yml`](yaml/README.md) - Learn all about the ins and outs of `.gitlab-ci.yml`.
+- [Pipelines and jobs](pipelines.md): configure your GitLab CI/CD pipelines to build, test, and deploy your application.
+- Runners: The [GitLab Runner](https://docs.gitlab.com/runner/) is responsible by running the jobs in your CI/CD pipeline. On GitLab.com, Shared Runners are enabled by default, so
+you don't need to set up anything to start to use them with GitLab CI/CD.
+
+### Introduction to GitLab CI/CD
+
+- Article (2016-08-05): [Continuous Integration, Delivery, and Deployment with GitLab - Intro to CI/CD](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
+- Article (2015-12-14): [Getting started with GitLab and GitLab CI - Intro to CI](https://about.gitlab.com/2015/12/14/getting-started-with-gitlab-and-gitlab-ci/)
+- Article (2017-07-13): [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/)
+- Article (2017-05-22): [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/)
- **Videos:**
- - [Demo (Streamed live on Jul 17, 2017): GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195)
- - [Demo (March, 2017): how to get started using CI/CD with GitLab](https://about.gitlab.com/2017/03/13/ci-cd-demo/)
- - [Webcast (April, 2016): getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/)
+ - Demo (Streamed live on Jul 17, 2017): [GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195)
+ - Demo (March, 2017): [How to get started using CI/CD with GitLab](https://about.gitlab.com/2017/03/13/ci-cd-demo/)
+ - Webcast (April, 2016): [Getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/)
- **Third-party videos:**
- [Intégration continue avec GitLab (September, 2016)](https://www.youtube.com/watch?v=URcMBXjIr24&t=13s)
- [GitLab CI for Minecraft Plugins (July, 2016)](https://www.youtube.com/watch?v=Z4pcI9F8yf8)
-## Reference guides
+### Why GitLab CI/CD?
-Once you get familiar with the getting started guides, you'll find yourself
-digging into specific reference guides.
+ - Article (2016-10-17): [Why We Chose GitLab CI for our CI/CD Solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/)
+ - Article (2016-07-22): [Building our web-app on GitLab CI: 5 reasons why Captain Train migrated from Jenkins to GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/)
-- [`.gitlab-ci.yml` reference](yaml/README.md) - Learn all about the ins and
- outs of `.gitlab-ci.yml` definitions
-- [CI Variables](variables/README.md) - Learn how to use variables defined in
+## Exploring GitLab CI/CD
+
+- [CI/CD Variables](variables/README.md) - Learn how to use variables defined in
your `.gitlab-ci.yml` or secured ones defined in your project's settings
- **The permissions model** - Learn about the access levels a user can have for
performing certain CI actions
- [User permissions](../user/permissions.md#gitlab-ci)
- [Job permissions](../user/permissions.md#job-permissions)
-
-## Auto DevOps
-
-- [Auto DevOps](../topics/autodevops/index.md)
-
-## GitLab CI + Docker
-
-Leverage the power of Docker to run your CI pipelines.
-
-- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
-- [Use CI to build Docker images](docker/using_docker_build.md)
-- [CI services (linked Docker containers)](services/README.md)
-- **Articles:**
- - [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
+- [Configure a Runner, the application that runs your jobs](runners/README.md)
+- Article (2016-03-01): [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
+- Article (2016-07-29): [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
+- Article (2016-08-26): [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
+- Article (2016-05-23): [Introduction to GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/)
## Advanced use
-Once you get familiar with the basics of GitLab CI, it's time to dive in and
+Once you get familiar with the basics of GitLab CI/CD, it's time to dive in and
learn how to leverage its potential even more.
-- [Environments and deployments](environments.md) - Separate your jobs into
+- [Environments and deployments](environments.md): Separate your jobs into
environments and use them for different purposes like testing, building and
deploying
- [Job artifacts](../user/project/pipelines/job_artifacts.md)
-- [Git submodules](git_submodules.md) - How to run your CI jobs when Git
+- [Git submodules](git_submodules.md): How to run your CI jobs when Git
submodules are involved
-- [Auto deploy](autodeploy/index.md)
- [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger pipelines through the GitLab API](triggers/README.md)
- [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md)
-## Review Apps
+## GitLab CI/CD for Docker
-- [Review Apps](review_apps/index.md)
-- **Articles:**
- - [Introducing Review Apps](https://about.gitlab.com/2016/11/22/introducing-review-apps/)
- - [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/)
+Leverage the power of Docker to run your CI pipelines.
-## GitLab CI for GitLab Pages
+- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
+- [Use CI to build Docker images](docker/using_docker_build.md)
+- [CI services (linked Docker containers)](services/README.md)
+- Article (2016-03-01): [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
-See the topic on [GitLab Pages](../user/project/pages/index.md).
+## Review Apps
-## Special configuration
+- [Review Apps documentation](review_apps/index.md)
+- Article (2016-11-22): [Introducing Review Apps](https://about.gitlab.com/2016/11/22/introducing-review-apps/)
+- [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/)
-You can change the default behavior of GitLab CI in your whole GitLab instance
-as well as in each project.
+## Auto DevOps
-- **Project specific**
- - [Pipelines settings](../user/project/pipelines/settings.md)
- - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
-- **Affecting the whole GitLab instance**
- - [Continuous Integration admin settings](../user/admin_area/settings/continuous_integration.md)
+- [Auto DevOps](../topics/autodevops/index.md): Auto DevOps automatically detects, builds, tests, deploys, and monitors your applications.
+
+## GitLab CI for GitLab Pages
+
+See the documentation on [GitLab Pages](../user/project/pages/index.md).
## Examples
->**Note:**
-A collection of `.gitlab-ci.yml` files is maintained at the
-[GitLab CI Yml project][gitlab-ci-templates].
-If your favorite programming language or framework is missing we would love
-your help by sending a merge request with a `.gitlab-ci.yml`.
-
-Here is an collection of tutorials and guides on setting up your CI pipeline.
-
-- [GitLab CI examples](examples/README.md) for the following languages and frameworks:
- - [PHP](examples/php.md)
- - [Ruby](examples/test-and-deploy-ruby-application-to-heroku.md)
- - [Python](examples/test-and-deploy-python-application-to-heroku.md)
- - [Clojure](examples/test-clojure-application.md)
- - [Scala](examples/test-scala-application.md)
- - [Phoenix](examples/test-phoenix-application.md)
- - [Run PHP Composer & NPM scripts then deploy them to a staging server](examples/deployment/composer-npm-deploy.md)
- - [Analyze code quality with the Code Climate CLI](examples/code_climate.md)
-- **Articles**
- - [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](examples/laravel_with_gitlab_and_envoy/index.md)
- - [How to deploy Maven projects to Artifactory with GitLab CI/CD](examples/artifactory_and_gitlab/index.md)
- - [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- - [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
- - [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
- - [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
- - [Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
- - [CI/CD with GitLab in action](https://about.gitlab.com/2017/03/13/ci-cd-demo/)
- - [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
-- **Miscellaneous**
- - [Using `dpl` as deployment tool](examples/deployment/README.md)
- - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
- - [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/)
+Check the [GitLab CI/CD examples](examples/README.md) for a collection of tutorials and guides on setting up your CI/CD pipeline for various programming languages, frameworks,
+and operating systems.
## Integrations
-- **Articles:**
- - [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/)
- - [Getting Started with GitLab and Shippable Continuous Integration](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/)
- - [GitLab Partners with DigitalOcean to make Continuous Integration faster, safer, and more affordable](https://about.gitlab.com/2016/04/19/gitlab-partners-with-digitalocean-to-make-continuous-integration-faster-safer-and-more-affordable/)
+- Article (2016-06-09): [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/)
+- Article (2016-05-05): [Getting Started with GitLab and Shippable Continuous Integration](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/)
+- Article (2016-04-19): [GitLab Partners with DigitalOcean to make Continuous Integration faster, safer, and more affordable](https://about.gitlab.com/2016/04/19/gitlab-partners-with-digitalocean-to-make-continuous-integration-faster-safer-and-more-affordable/)
-## Why GitLab CI?
+## Special configuration (GitLab admin)
-- **Articles:**
- - [Why We Chose GitLab CI for our CI/CD Solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/)
- - [Building our web-app on GitLab CI: 5 reasons why Captain Train migrated from Jenkins to GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/)
+As a GitLab administrator, you can change the default behavior of GitLab CI/CD in
+your whole GitLab instance as well as in each project.
+
+- [Continuous Integration admin settings](../administration/index.md#continuous-integration-settings)
+- **Project specific:**
+ - [Pipelines settings](../user/project/pipelines/settings.md)
+ - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
+- **Affecting the whole GitLab instance:**
+ - [Continuous Integration admin settings](../user/admin_area/settings/continuous_integration.md)
## Breaking changes
diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md
index 474cb28b9e4..7102af5c529 100644
--- a/doc/ci/autodeploy/index.md
+++ b/doc/ci/autodeploy/index.md
@@ -37,6 +37,8 @@ during the deployment.
We made a [simple guide](quick_start_guide.md) to using Auto Deploy with GitLab.com.
+For a demonstration of GitLab Auto Deploy, read the blog post [Auto Deploy from GitLab to an OpenShift Container Cluster](https://about.gitlab.com/2017/05/16/devops-containers-gitlab-openshift/)
+
## Supported templates
The list of supported auto deploy templates is available in the
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index c03e16b1b38..58c4a71cef9 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -455,7 +455,7 @@ Mappings are defined as entries in the root YAML array, and are identified by a
- Literal periods (`.`) should be escaped as `\.`.
- `public`
- a string, starting and ending with `'`.
- - Can include `\N` expressions to refer to capture groups in the `source` regular expression in order of their occurence, starting with `\1`.
+ - Can include `\N` expressions to refer to capture groups in the `source` regular expression in order of their occurrence, starting with `\1`.
The public path for a source path is determined by finding the first `source` expression that matches it, and returning the corresponding `public` path, replacing the `\N` expressions with the values of the `()` capture groups if appropriate.
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index d4590d0f495..0109e77935a 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -2,81 +2,72 @@
comments: false
---
-# GitLab CI Examples
+# GitLab CI/CD Examples
-A collection of `.gitlab-ci.yml` files is maintained at the [GitLab CI Yml project][gitlab-ci-templates].
-If your favorite programming language or framework are missing we would love your help by sending a merge request
-with a `.gitlab-ci.yml`.
+A collection of `.gitlab-ci.yml` template files is maintained at the [GitLab CI/CD YAML project][gitlab-ci-templates]. When you create a new file via the UI,
+GitLab will give you the option to choose one of the templates existent on this project.
+If your favorite programming language or framework are missing we would love your
+help by sending a merge request with a new `.gitlab-ci.yml` to this project.
-Apart from those, here is an collection of tutorials and guides on setting up your CI pipeline:
+There's also a collection of repositories with [example projects](https://gitlab.com/gitlab-examples) for various languages. You can fork an adjust them to your own needs.
## Languages, frameworks, OSs
-### PHP
+- **PHP**:
+ - [Testing a PHP application](php.md)
+ - [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md)
+ - [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md)
+- **Ruby**: [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
+- **Python**: [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
+- **Java**: [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
+- **Scala**: [Test a Scala application](test-scala-application.md)
+- **Clojure**: [Test a Clojure application](test-clojure-application.md)
+- **Elixir**:
+ - [Test a Phoenix application](test-phoenix-application.md)
+ - [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
+- **iOS and macOS**:
+ - [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
+ - [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/)
+- **Android**: [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
+- **Debian**: [Continuous Deployment with GitLab: how to build and deploy a Debian Package with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
+- **Maven**: [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md)
+
+### Miscellaneous
-- [Testing a PHP application](php.md)
-- [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md)
-- [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md)
-
-### Ruby
-
-- [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
-
-### Python
-
-- [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
-
-### Java
-
-- [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
-
-### Scala
-
-- [Test a Scala application](test-scala-application.md)
-
-### Clojure
-
-- [Test a Clojure application](test-clojure-application.md)
-
-### Elixir
+- [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 a Phoenix application](test-phoenix-application.md)
-- [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
+### Code quality analysis
-### iOS
+[Analyze code quality with the Code Climate CLI](code_climate.md).
-- [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
+### Static Application Security Testing (SAST)
-### Android
+- **(EEU)** [Scan your code for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/sast.html)
+- [Scan your Docker images for vulnerabilities](sast_docker.md)
-- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
+### Dynamic Application Security Testing (DAST)
-### Code quality analysis
+Scan your app for vulnerabilities with GitLab [Dynamic Application Security Testing (DAST)](dast.md).
-- [Analyze code quality with the Code Climate CLI](code_climate.md)
+### Browser Performance Testing with Sitespeed.io
-### Other
+Analyze your [browser performance with Sitespeed.io](browser_performance.md).
-- [Using `dpl` as deployment tool](deployment/README.md)
-- [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
-- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
-- [Continuous Deployment with GitLab: how to build and deploy a Debian Package with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
-- [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md)
+### GitLab CI/CD for Review Apps
-## GitLab CI/CD for GitLab Pages
+- [Example project](https://gitlab.com/gitlab-examples/review-apps-nginx/) that shows how to use GitLab CI/CD for [Review Apps](../review_apps/index.html).
+- [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/)
-- [Example projects](https://gitlab.com/pages)
-- [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](../../user/project/pages/getting_started_part_four.md)
-- [SSGs Part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/):
-examples for Ruby-, NodeJS-, Python-, and GoLang-based SSGs
-- [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
-- [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
+### GitLab CI/CD for GitLab Pages
See the documentation on [GitLab Pages](../../user/project/pages/index.md) for a complete overview.
-## More
+## Contributing
-Contributions are very much welcomed! You can help your favorite programming
-language and GitLab by sending a merge request with a guide for that language.
+Contributions are very welcome! You can help your favorite programming
+language users and GitLab by sending a merge request with a guide for that language.
+You may want to apply for the [GitLab Community Writers Program](https://about.gitlab.com/community-writers/)
+to get paid for writing complete articles for GitLab.
[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml
diff --git a/doc/ci/examples/dast.md b/doc/ci/examples/dast.md
new file mode 100644
index 00000000000..7bf647bbb8b
--- /dev/null
+++ b/doc/ci/examples/dast.md
@@ -0,0 +1,40 @@
+# Dynamic Application Security Testing with GitLab CI/CD
+
+[Dynamic Application Security Testing (DAST)](https://en.wikipedia.org/wiki/Dynamic_program_analysis)
+is using the popular open source tool [OWASP ZAProxy](https://github.com/zaproxy/zaproxy)
+to perform an analysis on your running web application.
+
+It can be very useful combined with [Review Apps](../review_apps/index.md).
+
+## Example
+
+All you need is a GitLab Runner with the Docker executor (the shared Runners on
+GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
+called `dast`:
+
+```yaml
+dast:
+ image: owasp/zap2docker-stable
+ variables:
+ website: "https://example.com"
+ script:
+ - mkdir /zap/wrk/
+ - /zap/zap-baseline.py -J gl-dast-report.json -t $website || true
+ - cp /zap/wrk/gl-dast-report.json .
+ artifacts:
+ paths: [gl-dast-report.json]
+```
+
+The above example will create a `dast` job in your CI/CD pipeline which will run
+the tests on the URL defined in the `website` variable (change it to use your
+own) and finally write the results in the `gl-dast-report.json` file. You can
+then download and analyze the report artifact in JSON format.
+
+TIP: **Tip:**
+Starting with [GitLab Enterprise Edition Ultimate][ee] 10.4, this information will
+be automatically extracted and shown right in the merge request widget. To do
+so, the CI job must be named `dast` and the artifact path must be
+`gl-dast-report.json`.
+[Learn more about DAST results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/dast.html).
+
+[ee]: https://about.gitlab.com/gitlab-ee/
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index 6768a2e012f..a2ba29a4ee2 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -167,7 +167,7 @@ Finally, push to GitLab and let the tests begin!
### Test against different PHP versions in Shell builds
The [phpenv][] project allows you to easily manage different versions of PHP
-each with its own config. This is specially usefull when testing PHP projects
+each with its own config. This is especially useful when testing PHP projects
with the Shell executor.
You will have to install it on your build machine under the `gitlab-runner`
@@ -227,7 +227,7 @@ following in your `.gitlab-ci.yml`:
...
# Composer stores all downloaded packages in the vendor/ directory.
-# Do not use the following if the vendor/ directory is commited to
+# Do not use the following if the vendor/ directory is committed to
# your git repository.
cache:
paths:
diff --git a/doc/ci/examples/sast_docker.md b/doc/ci/examples/sast_docker.md
new file mode 100644
index 00000000000..d99cfe93afa
--- /dev/null
+++ b/doc/ci/examples/sast_docker.md
@@ -0,0 +1,55 @@
+# Static Application Security Testing for Docker containers with GitLab CI/CD
+
+You can check your Docker images (or more precisely the containers) for known
+vulnerabilities by using [Clair](https://github.com/coreos/clair) and
+[clair-scanner](https://github.com/arminc/clair-scanner), two open source tools
+for Vulnerability Static Analysis for containers.
+
+All you need is a GitLab Runner with the Docker executor (the shared Runners on
+GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
+called `sast:container`:
+
+```yaml
+sast:container:
+ image: docker:latest
+ variables:
+ DOCKER_DRIVER: overlay2
+ ## Define two new variables based on GitLab's CI/CD predefined variables
+ ## https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
+ CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
+ CI_APPLICATION_TAG: $CI_COMMIT_SHA
+ allow_failure: true
+ services:
+ - docker:dind
+ script:
+ - docker run -d --name db arminc/clair-db:latest
+ - docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1
+ - apk add -U wget ca-certificates
+ - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
+ - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+ - mv clair-scanner_linux_amd64 clair-scanner
+ - chmod +x clair-scanner
+ - touch clair-whitelist.yml
+ - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
+ artifacts:
+ paths: [gl-sast-container-report.json]
+```
+
+The above example will create a `sast:container` job in your CI/CD pipeline, pull
+the image from the [Container Registry](../../user/project/container_registry.md)
+(whose name is defined from the two `CI_APPLICATION_` variables) and scan it
+for possible vulnerabilities. The report will be saved as an artifact that you
+can later download and analyze.
+
+If you want to whitelist some specific vulnerabilities, you can do so by defining
+them in a [YAML file](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file),
+in our case its named `clair-whitelist.yml`.
+
+TIP: **Tip:**
+Starting with [GitLab Enterprise Edition Ultimate][ee] 10.4, this information will
+be automatically extracted and shown right in the merge request widget. To do
+so, the CI/CD job must be named `sast:container` and the artifact path must be
+`gl-sast-container-report.json`.
+[Learn more on application security testing results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html).
+
+[ee]: https://about.gitlab.com/gitlab-ee/
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index a6ed1c54e16..a433cd5a5dd 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -42,7 +42,7 @@ production:
This project has three jobs:
1. `test` - used to test Django application,
2. `staging` - used to automatically deploy staging environment every push to `master` branch
-3. `production` - used to automatically deploy production environmnet for every created tag
+3. `production` - used to automatically deploy production environment for every created tag
## Store API keys
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index 10fd2616fab..7f9ab1f3a5e 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -10,6 +10,7 @@ This is what the `.gitlab-ci.yml` file looks like for this project:
```yaml
test:
+ stage: test
script:
- apt-get update -qy
- apt-get install -y nodejs
@@ -18,7 +19,7 @@ test:
- bundle exec rake test
staging:
- type: deploy
+ stage: deploy
script:
- gem install dpl
- dpl --provider=heroku --app=gitlab-ci-ruby-test-staging --api-key=$HEROKU_STAGING_API_KEY
@@ -26,7 +27,7 @@ staging:
- master
production:
- type: deploy
+ stage: deploy
script:
- gem install dpl
- dpl --provider=heroku --app=gitlab-ci-ruby-test-prod --api-key=$HEROKU_PRODUCTION_API_KEY
diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md
index f6c81b076bc..7e49721daf1 100644
--- a/doc/ci/examples/test-phoenix-application.md
+++ b/doc/ci/examples/test-phoenix-application.md
@@ -53,4 +53,3 @@ If you do not have any migrations yet, you will need to create an empty
## Sources
- https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142
-- https://davejlong.com/ci-with-phoenix-and-gitlab/
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index f621bf07251..e504b81eae8 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -135,9 +135,9 @@ Clicking on it you will be directed to the jobs page for that specific commit.
![Single commit jobs page](img/single_commit_status_pending.png)
-Notice that there are two jobs pending which are named after what we wrote in
-`.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured
-yet for these jobs.
+Notice that there is a pending job which is named after what we wrote in
+`.gitlab-ci.yml`. "stuck" indicates that there is no Runner configured
+yet for this job.
The next step is to configure a Runner so that it picks the pending jobs.
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index df66810a838..03aa6ff8e7c 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -144,6 +144,28 @@ To protect/unprotect Runners:
![specific Runners edit icon](img/protected_runners_check_box.png)
+## Manually clearing the Runners cache
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41249) in GitLab 10.4.
+
+GitLab Runners use [cache](../yaml/README.md#cache) to speed up the execution
+of your jobs by reusing existing data. This however, can sometimes lead to an
+inconsistent behavior.
+
+To start with a fresh copy of the cache, you can easily do it via GitLab's UI:
+
+1. Navigate to your project's **CI/CD > Pipelines** page.
+1. Click on the **Clear Runner caches** to clean up the cache.
+1. On the next push, your CI/CD job will use a new cache.
+
+That way, you don't have to change the [cache key](../yaml/README.md#cache-key)
+in your `.gitlab-ci.yml`.
+
+Behind the scenes, this works by increasing a counter in the database, and the
+value of that counter is used to create the key for the cache. After a push, a
+new key is generated and the old cache is not valid anymore. Eventually, the
+Runner's garbage collector will remove it form the filesystem.
+
## How shared Runners pick jobs
Shared Runners abide to a process queue we call fair usage. The fair usage
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index b8df0bfba20..693c8e9ef18 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -194,7 +194,7 @@ before_script:
##
## You can optionally disable host key checking. Be aware that by adding that
- ## you are suspectible to man-in-the-middle attacks.
+ ## you are susceptible to man-in-the-middle attacks.
## WARNING: Use this only with the Docker executor, if you use it with shell
## you will overwrite your user's SSH config.
##
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index b9d4a2098ed..06f69938aae 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -87,7 +87,7 @@ future GitLab releases.**
## 9.0 Renaming
-To follow conventions of naming across GitLab, and to futher move away from the
+To follow conventions of naming across GitLab, and to further move away from the
`build` term and toward `job` CI variables have been renamed for the 9.0
release.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 32464cbb259..82052cc0376 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -93,7 +93,7 @@ be an array or a multi-line string.
> Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
`after_script` is used to define the command that will be run after for all
-jobs. This has to be an array or a multi-line string.
+jobs, including failed ones. This has to be an array or a multi-line string.
> **Note:**
The `before_script` and the main `script` are concatenated and run in a single context/container.
@@ -258,7 +258,7 @@ The `cache:key` variable can use any of the [predefined variables](../variables/
The default key is **default** across the project, therefore everything is
shared between each pipelines and jobs by default, starting from GitLab 9.0.
->**Note:** The `cache:key` variable cannot contain the `/` character.
+>**Note:** The `cache:key` variable cannot contain the `/` character, or the equivalent URI encoded `%2F`; a value made only of dots (`.`, `%2E`) is also forbidden.
---
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index 4b9791c95bc..5a784b6de06 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -61,7 +61,7 @@ against EE.
1. Tries to apply it to current EE `master`
1. If it applies cleanly, the job succeeds
-In the case where the job fails, it means you should create a `ee-<ce_branch>`
+In the case where the job fails, it means you should create an `ee-<ce_branch>`
or `<ce_branch>-ee` branch, push it to EE and open a merge request against EE
`master`.
At this point if you retry the failing job in your CE merge request, it should
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index fd2b9d0e908..af2026c483e 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -123,7 +123,7 @@ roughly be as follows:
scheduling jobs for newly created data.
1. In a post-deployment migration you'll need to ensure no jobs remain. To do
so you can use `Gitlab::BackgroundMigration.steal` to process any remaining
- jobs before continueing.
+ jobs before continuing.
1. Remove the old column.
## Example
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 18f4177a5e5..c1f783ce877 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -12,9 +12,9 @@ following format:
```yaml
---
-title: "Going through change[log]s"
+title: "Change[log]s"
merge_request: 1972
-author: Ozzy Osbourne
+author: Black Sabbath
type: added
```
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 9cb1f708a6a..cfeeed2506d 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -34,7 +34,6 @@ The table below shows what kind of documentation goes where.
| `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). |
| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. |
| `doc/topics/` | Indexes per Topic (`doc/topics/topic-name/index.md`): all resources for that topic (user and admin documentation, articles, and third-party docs) |
-| `doc/articles/` | [Technical Articles](writing_documentation.md#technical-articles): user guides, admin guides, technical overviews, tutorials (`doc/articles/article-title/index.md`). |
---
@@ -67,11 +66,10 @@ The table below shows what kind of documentation goes where.
1. The `doc/topics/` directory holds topic-related technical content. Create
`doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary.
General user- and admin- related documentation, should be placed accordingly.
-1. For technical articles, place their images under `doc/articles/article-title/img/`.
---
-If you are unsure where a document should live, you can ping `@axil` in your
+If you are unsure where a document should live, you can ping `@axil` or `@marcia` in your
merge request.
## Text
@@ -108,8 +106,8 @@ merge request.
- Avoid adding things that show ephemeral statuses. For example, if a feature is
considered beta or experimental, put this info in a note, not in the heading.
- When introducing a new document, be careful for the headings to be
- grammatically and syntactically correct. It is advised to mention one or all
- of the following GitLab members for a review: `@axil`, `@rspeicher`, `@marcia`.
+ grammatically and syntactically correct. Mention one or all
+ of the following GitLab members for a review: `@axil` or `@marcia`.
This is to ensure that no document with wrong heading is going
live without an audit, thus preventing dead links and redirection issues when
corrected
@@ -203,7 +201,7 @@ You can combine one or more of the following:
- Keep all file names in lower case.
- Consider using PNG images instead of JPEG.
- Compress all images with <https://tinypng.com/> or similar tool.
-- Compress gifs with <https://ezgif.com/optimize> or similar toll.
+- Compress gifs with <https://ezgif.com/optimize> or similar tool.
- Images should be used (only when necessary) to _illustrate_ the description
of a process, not to _replace_ it.
@@ -330,6 +328,10 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
git grep -n "lfs/lfs_administration"
```
+NOTE: **Note:**
+If the document being moved has any Disqus comments on it, there are extra steps
+to follow documented just [below](#redirections-for-pages-with-disqus-comments).
+
Things to note:
- Since we also use inline documentation, except for the documentation itself,
@@ -342,6 +344,32 @@ Things to note:
documentation, sometimes it might be useful to search a path deeper.
- The `*.md` extension is not used when a document is linked to GitLab's
built-in help page, that's why we omit it in `git grep`.
+- Use the checklist on the documentation MR description template.
+
+### Redirections for pages with Disqus comments
+
+If the documentation page being relocated already has any Disqus comments,
+we need to preserve the Disqus thread.
+
+Disqus uses an identifier per page, and for docs.gitlab.com, the page identifier
+is configured to be the page URL. Therefore, when we change the document location,
+we need to preserve the old URL as the same Disqus identifier.
+
+To do that, add to the frontmatter the variable `redirect_from`,
+using the old URL as value. For example, let's say I moved the document
+available under `https://docs.gitlab.com/my-old-location/README.html` to a new location,
+`https://docs.gitlab.com/my-new-location/index.html`.
+
+Into the **new document** frontmatter add the following:
+
+```yaml
+---
+redirect_from: 'https://docs.gitlab.com/my-old-location/README.html'
+---
+```
+
+Note: it is necessary to include the file name in the `redirect_from` URL,
+even if it's `index.html` or `README.html`.
## Configuration documentation for source and Omnibus installations
@@ -392,7 +420,7 @@ the style below as a guide:
In this case:
- before each step list the installation method is declared in bold
-- three dashes (`---`) are used to create an horizontal line and separate the
+- three dashes (`---`) are used to create a horizontal line and separate the
two methods
- the code blocks are indented one or more spaces under the list item to render
correctly
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 1af839a27e1..f8cee89e650 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -87,9 +87,9 @@ still having access the class's implementation with `super`.
There are a few gotchas with it:
-- you should always add a `raise NotImplementedError unless defined?(super)`
- guard clause in the "overrider" method to ensure that if the method gets
- renamed in CE, the EE override won't be silently forgotten.
+- you should always [`extend ::Gitlab::Utils::Override`] and use `override` to
+ guard the "overrider" method to ensure that if the method gets renamed in
+ CE, the EE override won't be silently forgotten.
- when the "overrider" would add a line in the middle of the CE
implementation, you should refactor the CE method and split it in
smaller methods. Or create a "hook" method that is empty in CE,
@@ -134,6 +134,9 @@ There are a few gotchas with it:
guards:
``` ruby
module EE::Base
+ extend ::Gitlab::Utils::Override
+
+ override :do_something
def do_something
# Follow the above pattern to call super and extend it
end
@@ -174,10 +177,11 @@ implementation:
```ruby
module EE
- class ApplicationController
- def after_sign_out_path_for(resource)
- raise NotImplementedError unless defined?(super)
+ module ApplicationController
+ extend ::Gitlab::Utils::Override
+ override :after_sign_out_path_for
+ def after_sign_out_path_for(resource)
if Gitlab::Geo.secondary?
Gitlab::Geo.primary_node.oauth_logout_url(@geo_logout_state)
else
@@ -188,6 +192,8 @@ module EE
end
```
+[`extend ::Gitlab::Utils::Override`]: utilities.md#override
+
#### Use self-descriptive wrapper methods
When it's not possible/logical to modify the implementation of a
@@ -208,8 +214,8 @@ end
In EE, the implementation `ee/app/models/ee/users.rb` would be:
```ruby
+override :full_private_access?
def full_private_access?
- raise NotImplementedError unless defined?(super)
super || auditor?
end
```
diff --git a/doc/development/fe_guide/axios.md b/doc/development/fe_guide/axios.md
index 1daa6758171..34fa684c5d6 100644
--- a/doc/development/fe_guide/axios.md
+++ b/doc/development/fe_guide/axios.md
@@ -56,7 +56,7 @@ To help us mock the responses we need we use [axios-mock-adapter][axios-mock-ada
### Mock poll requests on tests with axios
-Because polling function requires an header object, we need to always include an object as the third argument:
+Because polling function requires a header object, we need to always include an object as the third argument:
```javascript
mock.onGet('/users').reply(200, { foo: 'bar' }, {});
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 6e9f18dd1c3..6c93c29124d 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -456,7 +456,7 @@ describe('Todos App', () => {
});
```
#### `mountComponent` helper
-There is an helper in `spec/javascripts/helpers/vue_mount_component_helper.js` that allows you to mount a component with the given props:
+There is a helper in `spec/javascripts/helpers/vue_mount_component_helper.js` that allows you to mount a component with the given props:
```javascript
import Vue from 'vue';
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index a235dd74909..243ac7f0c98 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -4,7 +4,7 @@ When writing migrations for GitLab, you have to take into account that
these will be ran by hundreds of thousands of organizations of all sizes, some with
many years of data in their database.
-In addition, having to take a server offline for a a upgrade small or big is a
+In addition, having to take a server offline for an upgrade small or big is a
big burden for most organizations. For this reason it is important that your
migrations are written carefully, can be applied online and adhere to the style
guide below.
diff --git a/doc/development/performance.md b/doc/development/performance.md
index e7c5a6ca07a..c4162a05b77 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -36,7 +36,8 @@ graphs/dashboards.
GitLab provides built-in tools to aid the process of improving performance:
-* [Sherlock](profiling.md#sherlock)
+* [Profiling](profiling.md)
+ * [Sherlock](profiling.md#sherlock)
* [GitLab Performance Monitoring](../administration/monitoring/performance/index.md)
* [Request Profiling](../administration/monitoring/performance/request_profiling.md)
* [QueryRecoder](query_recorder.md) for preventing `N+1` regressions
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index af79353b721..97c997e0568 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -4,6 +4,41 @@ To make it easier to track down performance problems GitLab comes with a set of
profiling tools, some of these are available by default while others need to be
explicitly enabled.
+## Profiling a URL
+
+There is a `Gitlab::Profiler.profile` method, and corresponding
+`bin/profile-url` script, that enable profiling a GET or POST request to a
+specific URL, either as an anonymous user (the default) or as a specific user.
+
+When using the script, command-line documentation is available by passing no
+arguments.
+
+When using the method in an interactive console session, any changes to the
+application code within that console session will be reflected in the profiler
+output.
+
+For example:
+
+```ruby
+Gitlab::Profiler.profile('/my-user')
+# Returns a RubyProf::Profile for the regular operation of this request
+class UsersController; def show; sleep 100; end; end
+Gitlab::Profiler.profile('/my-user')
+# Returns a RubyProf::Profile where 100 seconds is spent in UsersController#show
+```
+
+Passing a `logger:` keyword argument to `Gitlab::Profiler.profile` will send
+ActiveRecord and ActionController log output to that logger. Further options are
+documented with the method source.
+
+[GitLab-Profiler](https://gitlab.com/gitlab-com/gitlab-profiler) is a project
+that builds on this to add some additional niceties, such as allowing
+configuration with a single Yaml file for multiple URLs, and uploading of the
+profile and log output to S3.
+
+For GitLab.com, you can find the latest results here:
+<http://redash.gitlab.com/dashboard/gitlab-profiler-statistics>
+
## Sherlock
Sherlock is a custom profiling tool built into GitLab. Sherlock is _only_
@@ -27,13 +62,3 @@ Bullet will log query problems to both the Rails log as well as the Chrome
console.
As a follow up to finding `N+1` queries with Bullet, consider writing a [QueryRecoder test](query_recorder.md) to prevent a regression.
-
-## GitLab Profiler
-
-
-[Gitlab-Profiler](https://gitlab.com/gitlab-com/gitlab-profiler) was built to
-help developers understand why specific URLs of their application may be slow
-and to provide hard data that can help reduce load times.
-
-For GitLab.com, you can find the latest results here:
-<http://redash.gitlab.com/dashboard/gitlab-profiler-statistics>
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index ceff57276d2..dc88ce1522c 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -8,7 +8,7 @@ Note that if your db user does not have advanced privileges you must create the
bundle exec rake setup
```
-The `setup` task is a alias for `gitlab:setup`.
+The `setup` task is an alias for `gitlab:setup`.
This tasks calls `db:reset` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and finally it calls `db:seed_fu` to seed the database.
Note: `db:setup` calls `db:seed` but this does nothing.
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index edb8f372ea3..f5e8b4f146f 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -26,7 +26,6 @@ Here are some things to keep in mind regarding test performance:
- Use `.method` to describe class methods and `#method` to describe instance
methods.
- Use `context` to test branching logic.
-- Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](../gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
- Try to match the ordering of tests to the ordering within the class.
- Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines
to separate phases.
diff --git a/doc/development/utilities.md b/doc/development/utilities.md
index 951c3ef85ce..8f9aff1a35f 100644
--- a/doc/development/utilities.md
+++ b/doc/development/utilities.md
@@ -45,6 +45,51 @@ We developed a number of utilities to ease development.
[:hello, "world", :this, :crushes, "an entire", "hash"]
```
+## [`Override`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/override.rb)
+
+* This utility could help us check if a particular method would override
+ another method or not. It has the same idea of Java's `@Override` annotation
+ or Scala's `override` keyword. However we only do this check when
+ `ENV['STATIC_VERIFICATION']` is set to avoid production runtime overhead.
+ This is useful to check:
+
+ * If we have typos in overriding methods.
+ * If we renamed the overridden methods, making original overriding methods
+ overrides nothing.
+
+ Here's a simple example:
+
+ ``` ruby
+ class Base
+ def execute
+ end
+ end
+
+ class Derived < Base
+ extend ::Gitlab::Utils::Override
+
+ override :execute # Override check happens here
+ def execute
+ end
+ end
+ ```
+
+ This also works on modules:
+
+ ``` ruby
+ module Extension
+ extend ::Gitlab::Utils::Override
+
+ override :execute # Modules do not check this immediately
+ def execute
+ end
+ end
+
+ class Derived < Base
+ prepend Extension # Override check happens here, not in the module
+ end
+ ```
+
## [`StrongMemoize`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/strong_memoize.rb)
* Memoize the value even if it is `nil` or `false`.
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index d190ee1b0ff..797390a6845 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -27,7 +27,7 @@ View the [interactive example](http://codepen.io/awhildy/full/GNyEvM/) here.
### Dropdowns
-The dropdown menu should feel like it is appearing from the triggering element. Combining a position shift `400ms cubic-bezier(0.23, 1, 0.32, 1)` with a opacity animation `200ms linear` on the second half of the motion achieves this affect.
+The dropdown menu should feel like it is appearing from the triggering element. Combining a position shift `400ms cubic-bezier(0.23, 1, 0.32, 1)` with an opacity animation `200ms linear` on the second half of the motion achieves this affect.
View the [interactive example](http://codepen.io/awhildy/full/jVLJpb/) here.
diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md
index d396964e7c1..012c64be79f 100644
--- a/doc/development/ux_guide/components.md
+++ b/doc/development/ux_guide/components.md
@@ -108,7 +108,7 @@ Primary buttons communicate the main call to action. There should only be one ca
![Primary button example](img/button-primary.png)
#### Secondary
-Secondary buttons are for alternative commands. They should be conveyed by a button with an stroke, and no background fill.
+Secondary buttons are for alternative commands. They should be conveyed by a button with a stroke, and no background fill.
![Secondary button example](img/button-secondary.png)
@@ -181,7 +181,7 @@ A count element is used in navigation contexts where it is helpful to indicate t
## Lists
-Lists are used where ever there is a single column of information to display. Ths [issues list](https://gitlab.com/gitlab-org/gitlab-ce/issues) is an example of a important list in the GitLab UI.
+Lists are used where ever there is a single column of information to display. Ths [issues list](https://gitlab.com/gitlab-org/gitlab-ce/issues) is an example of an important list in the GitLab UI.
### Types
@@ -269,7 +269,7 @@ Modals are only used for having a conversation and confirmation with the user. T
* Modals contain the header, body, and actions.
* **Header(1):** The header title is a question instead of a descriptive phrase.
* **Body(2):** The content in body should never be ambiguous and unclear. It provides specific information.
- * **Actions(3):** Contains a affirmative action, a dismissive action, and an extra action. The order of actions from left to right: Dismissive action → Extra action → Affirmative action
+ * **Actions(3):** Contains an affirmative action, a dismissive action, and an extra action. The order of actions from left to right: Dismissive action → Extra action → Affirmative action
* Confirmations regarding labels should keep labeling styling.
* References to commits, branches, and tags should be **monospaced**.
diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md
index af842da7f62..070efdc15b5 100644
--- a/doc/development/ux_guide/copy.md
+++ b/doc/development/ux_guide/copy.md
@@ -27,7 +27,7 @@ This means that, as a rule, copy should be very short. A long message or label i
>**Example:**
Use `Add` instead of `Add issue` as a button label.
-Preferrably use context and placement of controls to make it obvious what clicking on them will do.
+Preferably use context and placement of controls to make it obvious what clicking on them will do.
---
diff --git a/doc/development/writing_documentation.md b/doc/development/writing_documentation.md
index 133ac0234cf..2a1d744668b 100644
--- a/doc/development/writing_documentation.md
+++ b/doc/development/writing_documentation.md
@@ -25,6 +25,26 @@ them to review it for you.
We use the [monthly release blog post](https://about.gitlab.com/handbook/marketing/blog/release-posts/#monthly-releases) as a changelog checklist to ensure everything
is documented.
+Whenever you submit a merge request for the documentation, use the documentation MR description template.
+
+### Documentation directory structure
+
+The documentation is structured based on the GitLab UI structure itself,
+separated by [`user`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/user),
+[`administrator`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/administration), and [`contributor`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/development).
+
+To learn where to place a new document, check the [documentation style guide](doc_styleguide.md#location-and-naming-of-documents).
+
+In order to have a [solid site structure](https://searchengineland.com/seo-benefits-developing-solid-site-structure-277456) for our documentation,
+all docs should be linked. Every new document should be cross-linked to its related documentation, and linked from its topic-related index, when existent.
+
+The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have
+been deprecated and the majority their docs have been moved to their correct location
+in small iterations. Please don't create new docs in these folders.
+
+To move a document from its location to another directory, read the section
+[changing document location](doc_styleguide.md#changing-document-location) of the doc style guide.
+
### Feature overview and use cases
Every major feature (regardless if present in GitLab Community or Enterprise editions)
diff --git a/doc/install/README.md b/doc/install/README.md
index 43197351db3..87f6969b415 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -25,15 +25,19 @@ the hardware requirements.
## Install GitLab on cloud providers
-- [Installing in Kubernetes](kubernetes/index.md) - Install GitLab into a Kubernetes
+- [Installing in Kubernetes](kubernetes/index.md): Install GitLab into a Kubernetes
Cluster using our official Helm Chart Repository.
- [Install GitLab on OpenShift](openshift_and_gitlab/index.md)
- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/) via [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/)
- [Install GitLab on Azure](azure/index.md)
- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md)
+- [Install GitLab on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/): video tutorial on
+the full process of installing GitLab on Google Container Engine (GKE), pushing an application to GitLab, building the app with GitLab CI/CD, and deploying to production.
- [Install on AWS](https://about.gitlab.com/aws/)
- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md) -
Quickly test any version of GitLab on DigitalOcean using Docker Machine.
+- [Getting started with GitLab and DigitalOcean](ttps://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): requirements, installation process, updates.
+- [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/): video demonstration on how to install GitLab on Kubernetes, build a project, create Review Apps, store Docker images in Container Registry, deploy to production on Kubernetes, and monitor with Prometheus.
## Database
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index 9cc4b56c932..7afe338ae8b 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -178,7 +178,7 @@ address. Read [IP address types and allocation methods in Azure][Azure-IP-Addres
At this stage you should have a running and fully operational VM. However, none of the services on
your VM (e.g. GitLab) will be publicly accessible via the internet until you have opened up the
-neccessary ports to enable access to those services.
+necessary ports to enable access to those services.
Ports are opened by adding _security rules_ to the **"Network security group"** (NSG) which our VM
has been assigned to. If you followed the process above, then Azure will have automatically created
@@ -436,4 +436,4 @@ Check out our other [Technical Articles][GitLab-Technical-Articles] or browse th
[SSH]: https://en.wikipedia.org/wiki/Secure_Shell
[PuTTY]: http://www.putty.org/
-[Using-SSH-In-Putty]: https://mediatemple.net/community/products/dv/204404604/using-ssh-in-putty- \ No newline at end of file
+[Using-SSH-In-Putty]: https://mediatemple.net/community/products/dv/204404604/using-ssh-in-putty-
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 2b7352d3561..18e29271d0f 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -64,7 +64,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
@@ -431,7 +431,7 @@ GitLab Shell is an SSH access and repository management software developed speci
**Note:** GitLab Shell application startup time can be greatly reduced by disabling RubyGems. This can be done in several manners:
* Export `RUBYOPT=--disable-gems` environment variable for the processes
-* Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommened for system-wide Ruby.
+* Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommended for system-wide Ruby.
* Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707)
### Install gitlab-workhorse
@@ -442,7 +442,7 @@ which is the recommended location.
sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
-You can specify a different Git repository by providing it as an extra paramter:
+You can specify a different Git repository by providing it as an extra parameter:
sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production
@@ -486,7 +486,7 @@ Make GitLab start on boot:
# 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
-You can specify a different Git repository by providing it as an extra paramter:
+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
@@ -656,7 +656,7 @@ Checkout the [GitLab Runner section](https://about.gitlab.com/gitlab-ci/#gitlab-
### Adding your Trusted Proxies
-If you are using a reverse proxy on an separate machine, you may want to add the
+If you are using a reverse proxy on a separate machine, you may want to add the
proxy to the trusted proxies list. Otherwise users will appear signed in from the
proxy's IP address.
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 8fba44aea02..448cbe1077d 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -15,6 +15,8 @@ In this tutorial, we will see how to deploy GitLab in OpenShift using GitLab's
official Docker image while getting familiar with the web interface and CLI
tools that will help us achieve our goal.
+For a video demonstration on installing GitLab on Openshift, check the article [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/).
+
---
## Prerequisites
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index baecf9455b0..4324b4ca0b8 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -7,6 +7,7 @@
- Ubuntu
- Debian
- CentOS
+- openSUSE
- Red Hat Enterprise Linux (please use the CentOS packages and instructions)
- Scientific Linux (please use the CentOS packages and instructions)
- Oracle Linux (please use the CentOS packages and instructions)
@@ -121,7 +122,7 @@ Existing users using GitLab with MySQL/MariaDB are advised to
### PostgreSQL Requirements
-As of GitLab 10.0, PostgreSQL 9.6 or newer (but less than 10) is required, and earlier versions are
+As of GitLab 10.0, PostgreSQL 9.6 or newer is required, and earlier versions are
not supported. We highly recommend users to use PostgreSQL 9.6 as this
is the PostgreSQL version used for development and testing.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 54c3e20d61d..50bb665216e 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -5,8 +5,8 @@
An application data backup creates an archive file that contains the database,
all repositories and all attachments.
-You can only restore a backup to **exactly the same version and type (CE/EE)**
-of GitLab on which it was created. The best way to migrate your repositories
+You can only restore a backup to **exactly the same version and type (CE/EE)**
+of GitLab on which it was created. The best way to migrate your repositories
from one server to another is through backup restore.
## Backup
@@ -14,6 +14,19 @@ from one server to another is through backup restore.
GitLab provides a simple command line interface to backup your whole installation,
and is flexible enough to fit your needs.
+### Requirements
+
+If you're using GitLab with the Omnibus package, you're all set. If you
+installed GitLab from source, make sure the following packages are installed:
+
+* rsync
+
+If you're using Ubuntu, you could run:
+
+```
+sudo apt-get install -y rsync
+```
+
### Backup timestamp
>**Note:**
@@ -431,7 +444,7 @@ The [restore prerequisites section](#restore-prerequisites) includes crucial
information. Make sure to read and test the whole restore process at least once
before attempting to perform it in a production environment.
-You can only restore a backup to **exactly the same version and type (CE/EE)** of
+You can only restore a backup to **exactly the same version and type (CE/EE)** of
GitLab that you created it on, for example CE 9.1.0.
### Restore prerequisites
@@ -511,7 +524,7 @@ sudo service gitlab restart
This procedure assumes that:
-- You have installed the **exact same version and type (CE/EE)** of GitLab
+- You have installed the **exact same version and type (CE/EE)** of GitLab
Omnibus with which the backup was created.
- You have run `sudo gitlab-ctl reconfigure` at least once.
- GitLab is running. If not, start it using `sudo gitlab-ctl start`.
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index f2a9b1d769b..9ba05c7815b 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -11,6 +11,7 @@ Your GitLab instance can perform HTTP POST requests on the following events:
- `user_remove_from_team`
- `user_create`
- `user_destroy`
+- `user_failed_login`
- `user_rename`
- `key_create`
- `key_destroy`
@@ -22,6 +23,8 @@ Your GitLab instance can perform HTTP POST requests on the following events:
The triggers for most of these are self-explanatory, but `project_update` and `project_rename` deserve some clarification: `project_update` is fired any time an attribute of a project is changed (name, description, tags, etc.) *unless* the `path` attribute is also changed. In that case, a `project_rename` is triggered instead (so that, for instance, if all you care about is the repo URL, you can just listen for `project_rename`).
+`user_failed_login` is sent whenever a **blocked** user attempts to login and denied access.
+
System hooks can be used, e.g. for logging or changing information in a LDAP server.
> **Note:**
@@ -196,6 +199,23 @@ Please refer to `group_rename` and `user_rename` for that case.
}
```
+**User failed login:**
+
+```json
+{
+ "event_name": "user_failed_login",
+ "created_at": "2017-10-03T06:08:48Z",
+ "updated_at": "2018-01-15T04:52:06Z",
+ "name": "John Smith",
+ "email": "user4@example.com",
+ "user_id": 26,
+ "username": "user4",
+ "state": "blocked"
+}
+```
+
+If the user is blocked via LDAP, `state` will be `ldap_blocked`.
+
**User renamed:**
```json
@@ -445,6 +465,135 @@ X-Gitlab-Event: System Hook
"total_commits_count": 0
}
```
+
+### Merge request events
+
+Triggered when a new merge request is created, an existing merge request was
+updated/merged/closed or a commit is added in the source branch.
+
+**Request header**:
+
+```
+X-Gitlab-Event: System Hook
+```
+
+```json
+{
+ "object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon"
+ },
+ "project": {
+ "name": "Example",
+ "description": "",
+ "web_url": "http://example.com/jsmith/example",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:jsmith/example.git",
+ "git_http_url": "http://example.com/jsmith/example.git",
+ "namespace": "Jsmith",
+ "visibility_level": 0,
+ "path_with_namespace": "jsmith/example",
+ "default_branch": "master",
+ "ci_config_path": "",
+ "homepage": "http://example.com/jsmith/example",
+ "url": "git@example.com:jsmith/example.git",
+ "ssh_url": "git@example.com:jsmith/example.git",
+ "http_url": "http://example.com/jsmith/example.git"
+ },
+ "object_attributes": {
+ "id": 90,
+ "target_branch": "master",
+ "source_branch": "ms-viewport",
+ "source_project_id": 14,
+ "author_id": 51,
+ "assignee_id": 6,
+ "title": "MS-Viewport",
+ "created_at": "2017-09-20T08:31:45.944Z",
+ "updated_at": "2017-09-28T12:23:42.365Z",
+ "milestone_id": null,
+ "state": "opened",
+ "merge_status": "unchecked",
+ "target_project_id": 14,
+ "iid": 1,
+ "description": "",
+ "updated_by_id": 1,
+ "merge_error": null,
+ "merge_params": {
+ "force_remove_source_branch": "0"
+ },
+ "merge_when_pipeline_succeeds": false,
+ "merge_user_id": null,
+ "merge_commit_sha": null,
+ "deleted_at": null,
+ "in_progress_merge_commit_sha": null,
+ "lock_version": 5,
+ "time_estimate": 0,
+ "last_edited_at": "2017-09-27T12:43:37.558Z",
+ "last_edited_by_id": 1,
+ "head_pipeline_id": 61,
+ "ref_fetched": true,
+ "merge_jid": null,
+ "source": {
+ "name": "Awesome Project",
+ "description": "",
+ "web_url": "http://example.com/awesome_space/awesome_project",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:awesome_space/awesome_project.git",
+ "git_http_url": "http://example.com/awesome_space/awesome_project.git",
+ "namespace": "root",
+ "visibility_level": 0,
+ "path_with_namespace": "awesome_space/awesome_project",
+ "default_branch": "master",
+ "ci_config_path": "",
+ "homepage": "http://example.com/awesome_space/awesome_project",
+ "url": "http://example.com/awesome_space/awesome_project.git",
+ "ssh_url": "git@example.com:awesome_space/awesome_project.git",
+ "http_url": "http://example.com/awesome_space/awesome_project.git"
+ },
+ "target": {
+ "name": "Awesome Project",
+ "description": "Aut reprehenderit ut est.",
+ "web_url": "http://example.com/awesome_space/awesome_project",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:awesome_space/awesome_project.git",
+ "git_http_url": "http://example.com/awesome_space/awesome_project.git",
+ "namespace": "Awesome Space",
+ "visibility_level": 0,
+ "path_with_namespace": "awesome_space/awesome_project",
+ "default_branch": "master",
+ "ci_config_path": "",
+ "homepage": "http://example.com/awesome_space/awesome_project",
+ "url": "http://example.com/awesome_space/awesome_project.git",
+ "ssh_url": "git@example.com:awesome_space/awesome_project.git",
+ "http_url": "http://example.com/awesome_space/awesome_project.git"
+ },
+ "last_commit": {
+ "id": "ba3e0d8ff79c80d5b0bbb4f3e2e343e0aaa662b7",
+ "message": "fixed readme",
+ "timestamp": "2017-09-26T16:12:57Z",
+ "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ },
+ "work_in_progress": false,
+ "total_time_spent": 0,
+ "human_total_time_spent": null,
+ "human_time_estimate": null
+ },
+ "labels": null,
+ "repository": {
+ "name": "git-gpg-test",
+ "url": "git@example.com:awesome_space/awesome_project.git",
+ "description": "",
+ "homepage": "http://example.com/awesome_space/awesome_project"
+ }
+}
+```
+
## Repository Update events
Triggered only once when you push to the repository (including tags).
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index e23c73f46fb..764ee0ca72c 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -20,9 +20,11 @@ project in an easy and automatic way:
1. [Auto Test](#auto-test)
1. [Auto Code Quality](#auto-code-quality)
1. [Auto SAST (Static Application Security Testing)](#auto-sast)
-1. [Auto Browser Performance Testing](#auto-browser-performance-testing)
+1. [Auto SAST for Docker images](#auto-sast-for-docker-images)
1. [Auto Review Apps](#auto-review-apps)
+1. [Auto DAST (Dynamic Application Security Testing)](#auto-dast)
1. [Auto Deploy](#auto-deploy)
+1. [Auto Browser Performance Testing](#auto-browser-performance-testing)
1. [Auto Monitoring](#auto-monitoring)
As Auto DevOps relies on many different components, it's good to have a basic
@@ -37,6 +39,8 @@ knowledge of the following:
Auto DevOps provides great defaults for all the stages; you can, however,
[customize](#customizing) almost everything to your needs.
+For an overview on the creation of Auto DevOps, read the blog post [From 2/3 of the Self-Hosted Git Market, to the Next-Generation CI System, to Auto DevOps](https://about.gitlab.com/2017/06/29/whats-next-for-gitlab-ci/).
+
## Prerequisites
TIP: **Tip:**
@@ -193,8 +197,10 @@ Auto Code Quality uses the open source
[`codeclimate` image](https://hub.docker.com/r/codeclimate/codeclimate/) to run
static analysis and other code checks on the current code. The report is
created, and is uploaded as an artifact which you can later download and check
-out. In GitLab Enterprise Edition Starter, differences between the source and
-target branches are
+out.
+
+In GitLab Enterprise Edition Starter, differences between the source and
+target branches are also
[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html).
### Auto SAST
@@ -207,21 +213,21 @@ analysis on the current code and checks for potential security issues. Once the
report is created, it's uploaded as an artifact which you can later download and
check out.
-Any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html).
+In GitLab Enterprise Edition Ultimate, any security warnings are also
+[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html).
-### Auto Browser Performance Testing
+### Auto SAST for Docker images
-> Introduced in [GitLab Enterprise Edition Premium][ee] 10.4.
-
-Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example:
+> Introduced in GitLab 10.4.
-```
-/
-/features
-/direction
-```
+Vulnerability Static Analysis for containers uses
+[Clair](https://github.com/coreos/clair) to run static analysis on a
+Docker image and checks for potential security issues. Once the report is
+created, it's uploaded as an artifact which you can later download and
+check out.
-In GitLab Enterprise Edition Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
+In GitLab Enterprise Edition Ultimate, any security warnings are also
+[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html).
### Auto Review Apps
@@ -249,6 +255,33 @@ up in the merge request widget for easy discovery. When the branch is deleted,
for example after the merge request is merged, the Review App will automatically
be deleted.
+### Auto DAST
+
+> Introduced in [GitLab Enterprise Edition Ultimate][ee] 10.4.
+
+Dynamic Application Security Testing (DAST) uses the
+popular open source tool [OWASP ZAProxy](https://github.com/zaproxy/zaproxy)
+to perform an analysis on the current code and checks for potential security
+issues. Once the report is created, it's uploaded as an artifact which you can
+later download and check out.
+
+In GitLab Enterprise Edition Ultimate, any security warnings are also
+[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/dast.html).
+
+### Auto Browser Performance Testing
+
+> Introduced in [GitLab Enterprise Edition Premium][ee] 10.4.
+
+Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example:
+
+```
+/
+/features
+/direction
+```
+
+In GitLab Enterprise Edition Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
+
### Auto Deploy
NOTE: **Note:**
diff --git a/doc/topics/git/how_to_install_git/index.md b/doc/topics/git/how_to_install_git/index.md
index cdf61057449..7fb578e9ea8 100644
--- a/doc/topics/git/how_to_install_git/index.md
+++ b/doc/topics/git/how_to_install_git/index.md
@@ -14,7 +14,7 @@ This article will show you how to install Git on macOS, Ubuntu Linux and Windows
Although it is easy to use the version of Git shipped with macOS
or install the latest version of Git on macOS by downloading it from the project website,
we recommend installing it via Homebrew to get access to
-an extensive selection of dependancy managed libraries and applications.
+an extensive selection of dependency managed libraries and applications.
If you are sure you don't need access to any additional development libraries
or don't have approximately 15gb of available disk space for Xcode and Homebrew
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
index f69e2e49f0c..2ca2bf743fb 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -44,7 +44,7 @@ We've gathered some resources to help you to get the best from Git with GitLab.
## Troubleshooting Git
-- [Numerous _undo_ possibilities in Git](../../articles/numerous_undo_possibilities_in_git/index.md)
+- [Numerous _undo_ possibilities in Git](numerous_undo_possibilities_in_git/index.md)
- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques to help you out.
## Branching strategies
diff --git a/doc/articles/numerous_undo_possibilities_in_git/img/branching.png b/doc/topics/git/numerous_undo_possibilities_in_git/img/branching.png
index 9a80c211c99..9a80c211c99 100644
--- a/doc/articles/numerous_undo_possibilities_in_git/img/branching.png
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/img/branching.png
Binary files differ
diff --git a/doc/articles/numerous_undo_possibilities_in_git/img/rebase_reset.png b/doc/topics/git/numerous_undo_possibilities_in_git/img/rebase_reset.png
index ac7ea9ecddc..ac7ea9ecddc 100644
--- a/doc/articles/numerous_undo_possibilities_in_git/img/rebase_reset.png
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/img/rebase_reset.png
Binary files differ
diff --git a/doc/articles/numerous_undo_possibilities_in_git/img/revert.png b/doc/topics/git/numerous_undo_possibilities_in_git/img/revert.png
index 13b3a35ca45..13b3a35ca45 100644
--- a/doc/articles/numerous_undo_possibilities_in_git/img/revert.png
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/img/revert.png
Binary files differ
diff --git a/doc/topics/git/numerous_undo_possibilities_in_git/index.md b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
new file mode 100644
index 00000000000..6a2f7b30dd3
--- /dev/null
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
@@ -0,0 +1,497 @@
+# Numerous undo possibilities in Git
+
+> **[Article Type](../../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
+> **Level:** intermediary ||
+> **Author:** [Crt Mori](https://gitlab.com/Letme) ||
+> **Publication date:** 2017-08-17
+
+## Introduction
+
+In this tutorial, we will show you different ways of undoing your work in Git, for which
+we will assume you have a basic working knowledge of. Check GitLab's
+[Git documentation](../index.md#git-documentation) for reference.
+Also, we will only provide some general info of the commands, which is enough
+to get you started for the easy cases/examples, but for anything more advanced please refer to the [Git book](https://git-scm.com/book/en/v2).
+
+We will explain a few different techniques to undo your changes based on the stage
+of the change in your current development. Also, keep in mind that [nothing in
+Git is really deleted.][git-autoclean-ref]
+This means that until Git automatically cleans detached commits (which cannot be
+accessed by branch or tag) it will be possible to view them with `git reflog` command
+and access them with direct commit-id. Read more about _[redoing the undo](#redoing-the-undo)_ on the section below.
+
+This guide is organized depending on the [stage of development][git-basics]
+where you want to undo your changes from and if they were shared with other developers
+or not. Because Git is tracking changes a created or edited file is in the unstaged state
+(if created it is untracked by Git). After you add it to a repository (`git add`) you put
+a file into the **staged** state, which is then committed (`git commit`) to your
+local repository. After that, file can be shared with other developers (`git push`).
+Here's what we'll cover in this tutorial:
+
+ - [Undo local changes](#undo-local-changes) which were not pushed to remote repository
+
+ - Before you commit, in both unstaged and staged state
+ - After you committed
+
+ - Undo changes after they are pushed to remote repository
+
+ - [Without history modification](#undo-remote-changes-without-changing-history) (preferred way)
+ - [With history modification](#undo-remote-changes-with-modifying-history) (requires
+ coordination with team and force pushes).
+
+ - [Usecases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable)
+ - [How to modify history](#how-modifying-history-is-done)
+ - [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits)
+
+
+### Branching strategy
+
+[Git][git-official] is a de-centralized version control system, which means that beside regular
+versioning of the whole repository, it has possibilities to exchange changes
+with other repositories. To avoid chaos with
+[multiple sources of truth][git-distributed], various
+development workflows have to be followed, and it depends on your internal
+workflow how certain changes or commits can be undone or changed.
+[GitLab Flow][gitlab-flow] provides a good
+balance between developers clashing with each other while
+developing the same feature and cooperating seamlessly, but it does not enable
+joined development of the same feature by multiple developers by default.
+When multiple developers develop the same feature on the same branch, clashing
+with every synchronization is unavoidable, but a proper or chosen Git Workflow will
+prevent that anything is lost or out of sync when feature is complete. You can also
+read through this blog post on [Git Tips & Tricks][gitlab-git-tips-n-tricks]
+to learn how to easily **do** things in Git.
+
+
+## Undo local changes
+
+Until you push your changes to any remote repository, they will only affect you.
+That broadens your options on how to handle undoing them. Still, local changes
+can be on various stages and each stage has a different approach on how to tackle them.
+
+
+### Unstaged local changes (before you commit)
+
+When a change is made, but it is not added to the staged tree, Git itself
+proposes a solution to discard changes to certain file.
+
+Suppose you edited a file to change the content using your favorite editor:
+
+```shell
+vim <file>
+```
+
+Since you did not `git add <file>` to staging, it should be under unstaged files (or
+untracked if file was created). You can confirm that with:
+
+```shell
+$ git status
+On branch master
+Your branch is up-to-date with 'origin/master'.
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: <file>
+no changes added to commit (use "git add" and/or "git commit -a")
+```
+
+At this point there are 3 options to undo the local changes you have:
+
+ - Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes)
+
+ ```shell
+ git stash
+ ```
+
+ - Discarding local changes (permanently) to a file
+
+ ```shell
+ git checkout -- <file>
+ ```
+
+ - Discard all local changes to all files permanently
+
+ ```shell
+ git reset --hard
+ ```
+
+
+Before executing `git reset --hard`, keep in mind that there is also a way to
+just temporary store the changes without committing them using `git stash`.
+This command resets the changes to all files, but it also saves them in case
+you would like to apply them at some later time. You can read more about it in
+[section below](#quickly-save-local-changes).
+
+### Quickly save local changes
+
+You are working on a feature when a boss drops by with an urgent task. Since your
+feature is not complete, but you need to swap to another branch, you can use
+`git stash` to save what you had done, swap to another branch, commit, push,
+test, then get back to previous feature branch, do `git stash pop` and continue
+where you left.
+
+The example above shows that discarding all changes is not always a preferred option,
+but Git provides a way to save them for later, while resetting the repository to state without
+them. This is achieved by Git stashing command `git stash`, which in fact saves your
+current work and runs `git reset --hard`, but it also has various
+additional options like:
+
+ - `git stash save`, which enables including temporary commit message, which will help you identify changes, among with other options
+ - `git stash list`, which lists all previously stashed commits (yes, there can be more) that were not `pop`ed
+ - `git stash pop`, which redoes previously stashed changes and removes them from stashed list
+ - `git stash apply`, which redoes previously stashed changes, but keeps them on stashed list
+
+### Staged local changes (before you commit)
+
+Let's say you have added some files to staging, but you want to remove them from the
+current commit, yet you want to retain those changes - just move them outside
+of the staging tree. You also have an option to discard all changes with
+`git reset --hard` or think about `git stash` [as described earlier.](#quickly-save-local-changes)
+
+Lets start the example by editing a file, with your favorite editor, to change the
+content and add it to staging
+
+```
+vim <file>
+git add <file>
+```
+
+The file is now added to staging as confirmed by `git status` command:
+
+```shell
+$ git status
+On branch master
+Your branch is up-to-date with 'origin/master'.
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: <file>
+```
+
+Now you have 4 options to undo your changes:
+
+ - Unstage the file to current commit (HEAD)
+
+ ```shell
+ git reset HEAD <file>
+ ```
+
+ - Unstage everything - retain changes
+
+ ```shell
+ git reset
+ ```
+
+ - Discard all local changes, but save them for [later](#quickly-save-local-changes)
+
+ ```shell
+ git stash
+ ```
+
+ - Discard everything permanently
+
+ ```shell
+ git reset --hard
+ ```
+
+## Committed local changes
+
+Once you commit, your changes are recorded by the version control system.
+Because you haven't pushed to your remote repository yet, your changes are
+still not public (or shared with other developers). At this point, undoing
+things is a lot easier, we have quite some workaround options. Once you push
+your code, you'll have less options to troubleshoot your work.
+
+### Without modifying history
+
+Through the development process some of the previously committed changes do not
+fit anymore in the end solution, or are source of the bugs. Once you find the
+commit which triggered bug, or once you have a faulty commit, you can simply
+revert it with `git revert commit-id`. This command inverts (swaps) the additions and
+deletions in that commit, so that it does not modify history. Retaining history
+can be helpful in future to notice that some changes have been tried
+unsuccessfully in the past.
+
+In our example we will assume there are commits `A`,`B`,`C`,`D`,`E` committed in this order: `A-B-C-D-E`,
+and `B` is the commit you want to undo. There are many different ways to identify commit
+`B` as bad, one of them is to pass a range to `git bisect` command. The provided range includes
+last known good commit (we assume `A`) and first known bad commit (where bug was detected - we will assume `E`).
+
+```shell
+git bisect A..E
+```
+
+Bisect will provide us with commit-id of the middle commit to test, and then guide us
+through simple bisection process. You can read more about it [in official Git Tools][git-debug]
+In our example we will end up with commit `B`, that introduced bug/error. We have
+4 options on how to remove it (or part of it) from our repository.
+
+- Undo (swap additions and deletions) changes introduced by commit `B`.
+
+ ```shell
+ git revert commit-B-id
+ ```
+
+- Undo changes on a single file or directory from commit `B`, but retain them in the staged state
+
+ ```shell
+ git checkout commit-B-id <file>
+ ```
+
+- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state
+
+ ```shell
+ git reset commit-B-id <file>
+ ```
+
+ - There is one command we also must not forget: **creating a new branch**
+ from the point where changes are not applicable or where the development has hit a
+ dead end. For example you have done commits `A-B-C-D` on your feature-branch
+ and then you figure `C` and `D` are wrong. At this point you either reset to `B`
+ and do commit `F` (which will cause problems with pushing and if forced pushed also with other developers)
+ since branch now looks `A-B-F`, which clashes with what other developers have locally (you will
+ [change history](#with-history-modification)), or you simply checkout commit `B` create
+ a new branch and do commit `F`. In the last case, everyone else can still do their work while you
+ have your new way to get it right and merge it back in later. Alternatively, with GitLab,
+ you can [cherry-pick](../../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
+ that commit into a new merge request.
+
+ ![Create a new branch to avoid clashing](img/branching.png)
+
+ ```shell
+ git checkout commit-B-id
+ git checkout -b new-path-of-feature
+ # Create <commit F>
+ git commit -a
+ ```
+
+### With history modification
+
+There is one command for history modification and that is `git rebase`. Command
+provides interactive mode (`-i` flag) which enables you to:
+
+ - **reword** commit messages (there is also `git commit --amend` for editing
+ last commit message)
+ - **edit** the commit content (changes introduced by commit) and message
+ - **squash** multiple commits into a single one, and have a custom or aggregated
+ commit message
+ - **drop** commits - simply delete them
+ - and few more options
+
+Let us check few examples. Again there are commits `A-B-C-D` where you want to
+delete commit `B`.
+
+- Rebase the range from current commit D to A:
+
+ ```shell
+ git rebase -i A
+ ```
+
+- Command opens your favorite editor where you write `drop` in front of commit
+ `B`, but you leave default `pick` with all other commits. Save and exit the
+ editor to perform a rebase. Remember: if you want to cancel delete whole
+ file content before saving and exiting the editor
+
+In case you want to modify something introduced in commit `B`.
+
+- Rebase the range from current commit D to A:
+
+ ```shell
+ git rebase -i A
+ ```
+
+- Command opens your favorite text editor where you write `edit` in front of commit
+ `B`, but leave default `pick` with all other commits. Save and exit the editor to
+ perform a rebase
+
+- Now do your edits and commit changes:
+
+ ```shell
+ git commit -a
+ ```
+
+You can find some more examples in [below section where we explain how to modify
+history](#how-modifying-history-is-done)
+
+
+### Redoing the Undo
+
+Sometimes you realize that the changes you undid were useful and you want them
+back. Well because of first paragraph you are in luck. Command `git reflog`
+enables you to *recall* detached local commits by referencing or applying them
+via commit-id. Although, do not expect to see really old commits in reflog, because
+Git regularly [cleans the commits which are *unreachable* by branches or tags][git-autoclean-ref].
+
+To view repository history and to track older commits you can use below command:
+
+```shell
+$ git reflog show
+
+# Example output:
+b673187 HEAD@{4}: merge 6e43d5987921bde189640cc1e37661f7f75c9c0b: Merge made by the 'recursive' strategy.
+eb37e74 HEAD@{5}: rebase -i (finish): returning to refs/heads/master
+eb37e74 HEAD@{6}: rebase -i (pick): Commit C
+97436c6 HEAD@{7}: rebase -i (start): checkout 97436c6eec6396c63856c19b6a96372705b08b1b
+...
+88f1867 HEAD@{12}: commit: Commit D
+97436c6 HEAD@{13}: checkout: moving from 97436c6eec6396c63856c19b6a96372705b08b1b to test
+97436c6 HEAD@{14}: checkout: moving from master to 97436c6
+05cc326 HEAD@{15}: commit: Commit C
+6e43d59 HEAD@{16}: commit: Commit B
+```
+
+Output of command shows repository history. In first column there is commit-id,
+in following column, number next to `HEAD` indicates how many commits ago something
+was made, after that indicator of action that was made (commit, rebase, merge, ...)
+and then on end description of that action.
+
+## Undo remote changes without changing history
+
+This topic is roughly same as modifying committed local changes without modifying
+history. **It should be the preferred way of undoing changes on any remote repository
+or public branch.** Keep in mind that branching is the best solution when you want
+to retain the history of faulty development, yet start anew from certain point. Branching
+enables you to include the existing changes in new development (by merging) and
+it also provides a clear timeline and development structure.
+
+![Use revert to keep branch flowing](img/revert.png)
+
+If you want to revert changes introduced in certain `commit-id` you can simply
+revert that `commit-id` (swap additions and deletions) in newly created commit:
+You can do this with
+
+```shell
+git revert commit-id
+```
+
+or creating a new branch:
+
+```shell
+git checkout commit-id
+git checkout -b new-path-of-feature
+```
+
+## Undo remote changes with modifying history
+
+This is useful when you want to *hide* certain things - like secret keys,
+passwords, SSH keys, etc. It is and should not be used to hide mistakes, as
+it will make it harder to debug in case there are some other bugs. The main
+reason for this is that you loose the real development progress. **Also keep in
+mind that, even with modified history, commits are just detached and can still be
+accessed through commit-id** - at least until all repositories perform
+the cleanup of detached commits (happens automatically).
+
+![Modifying history causes problems on remote branch](img/rebase_reset.png)
+
+### Where modifying history is generally acceptable
+
+Modified history breaks the development chain of other developers, as changed
+history does not have matching commits'ids. For that reason it should not
+be used on any public branch or on branch that *might* be used by other
+developers. When contributing to big open source repositories (e.g. [GitLab CE][gitlab-ce]),
+it is acceptable to *squash* commits into a single one, to present
+a nicer history of your contribution.
+Keep in mind that this also removes the comments attached to certain commits
+in merge requests, so if you need to retain traceability in GitLab, then
+modifying history is not acceptable.
+A feature-branch of a merge request is a public branch and might be used by
+other developers, but project process and rules might allow or require
+you to use `git rebase` (command that changes history) to reduce number of
+displayed commits on target branch after reviews are done (for example
+GitLab). There is a `git merge --squash` command which does exactly that
+(squashes commits on feature-branch to a single commit on target branch
+at merge).
+
+>**Note:**
+Never modify the commit history of `master` or shared branch
+
+### How modifying history is done
+
+After you know what you want to modify (how far in history or how which range of
+old commits), use `git rebase -i commit-id`. This command will then display all the commits from
+current version to chosen commit-id and allow modification, squashing, deletion
+of that commits.
+
+```shell
+$ git rebase -i commit1-id..commit3-id
+pick <commit1-id> <commit1-commit-message>
+pick <commit2-id> <commit2-commit-message>
+pick <commit3-id> <commit3-commit-message>
+
+# Rebase commit1-id..commit3-id onto <commit4-id> (3 command(s))
+#
+# Commands:
+# p, pick = use commit
+# r, reword = use commit, but edit the commit message
+# e, edit = use commit, but stop for amending
+# s, squash = use commit, but meld into previous commit
+# f, fixup = like "squash", but discard this commit's log message
+# x, exec = run command (the rest of the line) using shell
+# d, drop = remove commit
+#
+# These lines can be re-ordered; they are executed from top to bottom.
+#
+# If you remove a line here THAT COMMIT WILL BE LOST.
+#
+# However, if you remove everything, the rebase will be aborted.
+#
+# Note that empty commits are commented out
+```
+
+>**Note:**
+It is important to notice that comment from the output clearly states that, if
+you decide to abort, then do not just close your editor (as that will in-fact
+modify history), but remove all uncommented lines and save.
+
+That is one of the reasons why `git rebase` should be used carefully on
+shared and remote branches. But don't worry, there will be nothing broken until
+you push back to the remote repository (so you can freely explore the
+different outcomes locally).
+
+```shell
+# Modify history from commit-id to HEAD (current commit)
+git rebase -i commit-id
+```
+
+### Deleting sensitive information from commits
+
+Git also enables you to delete sensitive information from your past commits and
+it does modify history in the progress. That is why we have included it in this
+section and not as a standalone topic. To do so, you should run the
+`git filter-branch`, which enables you to rewrite history with
+[certain filters][git-filters-manual].
+This command uses rebase to modify history and if you want to remove certain
+file from history altogether use:
+
+```shell
+git filter-branch --tree-filter 'rm filename' HEAD
+```
+
+Since `git filter-branch` command might be slow on big repositories, there are
+tools that can use some of Git specifics to enable faster execution of common
+tasks (which is exactly what removing sensitive information file is about).
+An alternative is [BFG Repo-cleaner][bfg-repo-cleaner]. Keep in mind that these
+tools are faster because they do not provide a same fully feature set as `git filter-branch`
+does, but focus on specific usecases.
+
+## Conclusion
+
+There are various options of undoing your work with any version control system, but
+because of de-centralized nature of Git, these options are multiplied (or limited)
+depending on the stage of your process. Git also enables rewriting history, but that
+should be avoided as it might cause problems when multiple developers are
+contributing to the same codebase.
+
+<!-- Identifiers, in alphabetical order -->
+
+[bfg-repo-cleaner]: https://rtyley.github.io/bfg-repo-cleaner/
+[git-autoclean-ref]: https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery
+[git-basics]: https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository
+[git-debug]: https://git-scm.com/book/en/v2/Git-Tools-Debugging-with-Git
+[git-distributed]: https://git-scm.com/about/distributed
+[git-filters-manual]: https://git-scm.com/docs/git-filter-branch#_options
+[git-official]: https://git-scm.com/
+[gitlab-ce]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
+[gitlab-flow]: https://about.gitlab.com/2014/09/29/gitlab-flow/
+[gitlab-git-tips-n-tricks]: https://about.gitlab.com/2016/12/08/git-tips-and-tricks/
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index fbe7353c6ca..a9ccbf5a085 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -229,7 +229,7 @@ Our free on Premise solution with >100,000 users
### GitLab CI
-Our own Continuos Integration [feature](https://about.gitlab.com/gitlab-ci/) that is shipped with each instance
+Our own Continuous Integration [feature](https://about.gitlab.com/gitlab-ci/) that is shipped with each instance
### GitLab EE
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
index ddc853afded..47ccd0e6dbc 100644
--- a/doc/university/high-availability/aws/README.md
+++ b/doc/university/high-availability/aws/README.md
@@ -147,7 +147,7 @@ change which will be helpful is the database name for which we can use
## ElastiCache
EC is an in-memory hosted caching solution. Redis maintains its own
-persistance and is used for certain types of application.
+persistence and is used for certain types of application.
Let's choose the ElastiCache service in the Database section from our
AWS console. Now lets create a cache subnet group which will be very
@@ -311,7 +311,7 @@ Here is a tricky part though, when adding subnets we need to associate
public subnets instead of the private ones where our instances will
actually live.
-On the secruity group section let's create a new one named
+On the security group section let's create a new one named
`gitlab-loadbalancer-sec-group` and allow both HTTP ad HTTPS traffic
from anywhere.
diff --git a/doc/update/10.3-to-10.4.md b/doc/update/10.3-to-10.4.md
index 850cb3103f4..67b7e634c94 100644
--- a/doc/update/10.3-to-10.4.md
+++ b/doc/update/10.3-to-10.4.md
@@ -21,6 +21,8 @@ sudo service gitlab stop
### 2. Backup
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
```bash
cd /home/git/gitlab
diff --git a/doc/user/admin_area/monitoring/img/convdev_index.png b/doc/user/admin_area/monitoring/img/convdev_index.png
index ffe18d76c96..1bf1d6a83c9 100644
--- a/doc/user/admin_area/monitoring/img/convdev_index.png
+++ b/doc/user/admin_area/monitoring/img/convdev_index.png
Binary files differ
diff --git a/doc/user/index.md b/doc/user/index.md
index f239a15d441..01db8becc43 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -23,9 +23,20 @@ all the way through, from within the same platform.
Please check this page for an overview on [GitLab's features](https://about.gitlab.com/features/).
+### Concepts
+
+For an overview on concepts involved when developing code on GitLab,
+read the articles on:
+
+- [Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/).
+- [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
+- [Tutorial: It's all connected in GitLab](https://about.gitlab.com/2016/03/08/gitlab-tutorial-its-all-connected/): an overview on code collaboration with GitLab.
+- [Trends in Version Control Land: Microservices](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/).
+- [Trends in Version Control Land: Innersourcing](https://about.gitlab.com/2016/07/07/trends-version-control-innersourcing/).
+
## Use cases
-GitLab is a git-based platforms that integrates a great number of essential tools for software development and deployment, and project management:
+GitLab is a Git-based platform that integrates a great number of essential tools for software development and deployment, and project management:
- Code hosting in repositories with version control
- Track proposals for new implementations, bug reports, and feedback with a
@@ -58,12 +69,6 @@ and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.
You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
-### Articles
-
-For a complete workflow use case please check [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
-
-For more use cases please check our [Technical Articles](../articles/index.md).
-
## Projects
In GitLab, you can create [projects](project/index.md) for numerous reasons, such as, host
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 590c3f862fb..d3a2a7dcd14 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -212,7 +212,7 @@ Sign in and re-enable two-factor authentication as soon as possible.
For example, if a user is trying to access a GitLab instance from `first.host.xyz` and `second.host.xyz`:
- The user logs in via `first.host.xyz` and registers their U2F key.
- - The user logs out and attempts to log in via `first.host.xyz` - U2F authentication suceeds.
+ - The user logs out and attempts to log in via `first.host.xyz` - U2F authentication succeeds.
- The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
the U2F key has only been registered on `first.host.xyz`.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 130f7897b1a..e87b4403854 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -1,26 +1,28 @@
# Connecting GitLab with a Kubernetes cluster
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in 10.1.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in GitLab 10.1.
+
+Connect your project to Google Kubernetes Engine (GKE) or an existing Kubernetes
+cluster in a few steps.
With a cluster associated to your project, you can use Review Apps, deploy your
applications, run your pipelines, and much more, in an easy way.
-Connect your project to Google Kubernetes Engine (GKE) or your own Kubernetes
-cluster in a few steps.
-
-NOTE: **Note:**
-The Cluster integration will eventually supersede the
-[Kubernetes integration](../integrations/kubernetes.md). For the moment,
-you can create only one cluster.
+There are two options when adding a new cluster to your project; either associate
+your account with Google Kubernetes Engine (GKE) so that you can [create new
+clusters](#adding-and-creating-a-new-gke-cluster-via-gitlab) from within GitLab,
+or provide the credentials to an [existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster).
## Prerequisites
-In order to be able to manage your GKE cluster through GitLab, the following
-prerequisites must be met:
+In order to be able to manage your Kubernetes cluster through GitLab, the
+following prerequisites must be met.
+
+**For a cluster hosted on GKE:**
- The [Google authentication integration](../../../integration/google.md) must
be enabled in GitLab at the instance level. If that's not the case, ask your
- administrator to enable it.
+ GitLab administrator to enable it.
- Your associated Google account must have the right privileges to manage
clusters on GKE. That would mean that a [billing
account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
@@ -31,41 +33,88 @@ prerequisites must be met:
- You must have [Resource Manager
API](https://cloud.google.com/resource-manager/)
-If all of the above requirements are met, you can proceed to add a new GKE
+**For an existing Kubernetes cluster:**
+
+- Since the cluster is already created, there are no prerequisites.
+
+---
+
+If all of the above requirements are met, you can proceed to add a new Kubernetes
cluster.
-## Adding a cluster
+## Adding and creating a new GKE cluster via GitLab
+
+NOTE: **Note:**
+You need Master [permissions] and above to access the Clusters page.
+
+Before proceeding, make sure all [prerequisites](#prerequisites) are met.
+To add a new cluster hosted on GKE to your project:
+
+1. Navigate to your project's **CI/CD > Clusters** page.
+1. Click on **Add cluster**.
+1. Click on **Create with GKE**.
+1. Connect your Google account if you haven't done already by clicking the
+ **Sign in with Google** button.
+1. Fill in the requested values:
+ - **Cluster name** (required) - The name you wish to give the cluster.
+ - **GCP project ID** (required) - The ID of the project you created in your GCP
+ console that will host the Kubernetes cluster. This must **not** be confused
+ with the project name. Learn more about [Google Cloud Platform projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects).
+ - **Zone** - The [zone](https://cloud.google.com/compute/docs/regions-zones/)
+ under which the cluster will be created.
+ - **Number of nodes** - The number of nodes you wish the cluster to have.
+ - **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
+ of the Virtual Machine instance that the cluster will be based on.
+ - **Environment scope** - The [associated environment](#setting-the-environment-scope) to this cluster.
+1. Finally, click the **Create cluster** button.
+
+After a few moments, your cluster should be created. If something goes wrong,
+you will be notified.
+
+You can now proceed to install some pre-defined applications and then
+enable the Cluster integration.
+
+## Adding an existing Kubernetes cluster
NOTE: **Note:**
-You need Master [permissions] and above to add a cluster.
-
-There are two options when adding a new cluster; either use Google Kubernetes
-Engine (GKE) or provide the credentials to your own Kubernetes cluster.
-
-To add a new cluster:
-
-1. Navigate to your project's **CI/CD > Cluster** page
-1. If you want to let GitLab create a cluster on GKE for you, go through the
- following steps, otherwise skip to the next one.
- 1. Click on **Create with GKE**
- 1. Connect your Google account if you haven't done already by clicking the
- **Sign in with Google** button
- 1. Fill in the requested values:
- - **Cluster name** (required) - The name you wish to give the cluster.
- - **GCP project ID** (required) - The ID of the project you created in your GCP
- console that will host the Kubernetes cluster. This must **not** be confused
- with the project name. Learn more about [Google Cloud Platform projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects).
- - **Zone** - The [zone](https://cloud.google.com/compute/docs/regions-zones/)
- under which the cluster will be created.
- - **Number of nodes** - The number of nodes you wish the cluster to have.
- - **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
- of the Virtual Machine instance that the cluster will be based on.
- - **Project namespace** - The unique namespace for this project. By default you
- don't have to fill it in; by leaving it blank, GitLab will create one for you.
-1. If you want to use your own existing Kubernetes cluster, click on
- **Add an existing cluster** and fill in the details as described in the
- [Kubernetes integration](../integrations/kubernetes.md) documentation.
-1. Finally, click the **Create cluster** button
+You need Master [permissions] and above to access the Clusters page.
+
+To add an existing Kubernetes cluster to your project:
+
+1. Navigate to your project's **CI/CD > Clusters** page.
+1. Click on **Add cluster**.
+1. Click on **Add an existing cluster** and fill in the details:
+ - **Cluster name** (required) - The name you wish to give the cluster.
+ - **Environment scope** (required)- The
+ [associated environment](#setting-the-environment-scope) to this cluster.
+ - **API URL** (required) -
+ It's the URL that GitLab uses to access the Kubernetes API. Kubernetes
+ exposes several APIs, we want the "base" URL that is common to all of them,
+ e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`.
+ - **CA certificate** (optional) -
+ If the API is using a self-signed TLS certificate, you'll also need to include
+ the `ca.crt` contents here.
+ - **Token** -
+ GitLab authenticates against Kubernetes using service tokens, which are
+ scoped to a particular `namespace`. If you don't have a service token yet,
+ you can follow the
+ [Kubernetes documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
+ to create one. You can also view or create service tokens in the
+ [Kubernetes dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#config)
+ (under **Config > Secrets**).
+ - **Project namespace** (optional) - The following apply:
+ - By default you don't have to fill it in; by leaving it blank, GitLab will
+ create one for you.
+ - Each project should have a unique namespace.
+ - The project namespace is not necessarily the namespace of the secret, if
+ you're using a secret with broader permissions, like the secret from `default`.
+ - You should **not** use `default` as the project namespace.
+ - If you or someone created a secret specifically for the project, usually
+ with limited permissions, the secret's namespace and project namespace may
+ be the same.
+1. Finally, click the **Create cluster** button.
+
+The Kubernetes service takes the following parameters:
After a few moments, your cluster should be created. If something goes wrong,
you will be notified.
@@ -85,6 +134,91 @@ added directly to your configured cluster. Those applications are needed for
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications |
+## Setting the environment scope
+
+When adding more than one clusters, you need to differentiate them with an
+environment scope. The environment scope associates clusters and
+[environments](../../../ci/environments.md) in an 1:1 relationship similar to how the
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-secret-variables)
+work.
+
+The default environment scope is `*`, which means all jobs, regardless of their
+environment, will use that cluster. Each scope can only be used by a single
+cluster in a project, and a validation error will occur if otherwise.
+
+---
+
+For example, let's say the following clusters exist in a project:
+
+| Cluster | Environment scope |
+| ---------- | ------------------- |
+| Development| `*` |
+| Staging | `staging/*` |
+| Production | `production/*` |
+
+And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/README.md):
+
+```yaml
+stages:
+- test
+- deploy
+
+test:
+ stage: test
+ script: sh test
+
+deploy to staging:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: staging/$CI_COMMIT_REF_NAME
+ url: https://staging.example.com/
+
+deploy to production:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: production/$CI_COMMIT_REF_NAME
+ url: https://example.com/
+```
+
+The result will then be:
+
+- The development cluster will be used for the "test" job.
+- The staging cluster will be used for the "deploy to staging" job.
+- The production cluster will be used for the "deploy to production" job.
+
+## Multiple Kubernetes clusters
+
+> Introduced in [GitLab Enterprise Edition Premium][ee] 10.3.
+
+With GitLab EEP, you can associate more than one Kubernetes clusters to your
+project. That way you can have different clusters for different environments,
+like dev, staging, production, etc.
+
+To add another cluster, follow the same steps as described in [adding a
+Kubernetes cluster](#adding-a-kubernetes-cluster) and make sure to
+[set an environment scope](#setting-the-environment-scope) that will
+differentiate the new cluster with the rest.
+
+## Deployment variables
+
+The Kubernetes cluster integration exposes the following
+[deployment variables](../../../ci/variables/README.md#deployment-variables) in the
+GitLab CI/CD build environment:
+
+- `KUBE_URL` - Equal to the API URL.
+- `KUBE_TOKEN` - The Kubernetes token.
+- `KUBE_NAMESPACE` - The Kubernetes namespace is auto-generated if not specified.
+ The default value is `<project_name>-<project_id>`. You can overwrite it to
+ use different one if needed, otherwise the `KUBE_NAMESPACE` variable will
+ receive the default value.
+- `KUBE_CA_PEM_FILE` - Only present if a custom CA bundle was specified. Path
+ to a file containing PEM data.
+- `KUBE_CA_PEM` (deprecated) - Only if a custom CA bundle was specified. Raw PEM data.
+- `KUBECONFIG` - Path to a file containing `kubeconfig` for this deployment.
+ CA bundle would be embedded if specified.
+
## Enabling or disabling the Cluster integration
After you have successfully added your cluster information, you can enable the
@@ -111,4 +245,62 @@ To remove the Cluster integration from your project, simply click on the
**Remove integration** button. You will then be able to follow the procedure
and [add a cluster](#adding-a-cluster) again.
+## What you can get with the Kubernetes integration
+
+Here's what you can do with GitLab if you enable the Kubernetes integration.
+
+### Deploy Boards (EEP)
+
+> Available in [GitLab Enterprise Edition Premium][ee].
+
+GitLab's Deploy Boards offer a consolidated view of the current health and
+status of each CI [environment](../../../ci/environments.md) running on Kubernetes,
+displaying the status of the pods in the deployment. Developers and other
+teammates can view the progress and status of a rollout, pod by pod, in the
+workflow they already use without any need to access Kubernetes.
+
+[> Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+
+### Canary Deployments (EEP)
+
+> Available in [GitLab Enterprise Edition Premium][ee].
+
+Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
+and visualize your canary deployments right inside the Deploy Board, without
+the need to leave GitLab.
+
+[> Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+
+### Kubernetes monitoring
+
+Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
+[NGINX ingress](../integrations/prometheus_library/nginx.md) is also supported.
+
+[> Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
+
+### Auto DevOps
+
+Auto DevOps automatically detects, builds, tests, deploys, and monitors your
+applications.
+
+To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
+you will need the Kubernetes project integration enabled.
+
+[> Read more about Auto DevOps](../../../topics/autodevops/index.md)
+
+### Web terminals
+
+NOTE: **Note:**
+Introduced in GitLab 8.15. You must be the project owner or have `master` permissions
+to use terminals. Support is limited to the first container in the
+first pod of your environment.
+
+When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals)
+support to your [environments](../../../ci/environments.md). This is based on the `exec` functionality found in
+Docker and Kubernetes, so you get a new shell session within your existing
+containers. To use this integration, you should deploy to Kubernetes using
+the deployment variables above, ensuring any pods you create are labelled with
+`app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest!
+
[permissions]: ../../permissions.md
+[ee]: https://about.gitlab.com/gitlab-ee/
diff --git a/doc/user/project/integrations/emails_on_push.md b/doc/user/project/integrations/emails_on_push.md
index 18109fc049c..c01da883562 100644
--- a/doc/user/project/integrations/emails_on_push.md
+++ b/doc/user/project/integrations/emails_on_push.md
@@ -10,7 +10,7 @@ In the _Recipients_ area, provide a list of emails separated by commas.
You can configure any of the following settings depending on your preference.
-+ **Push events** - Email will be triggered when a push event is recieved
++ **Push events** - Email will be triggered when a push event is received
+ **Tag push events** - Email will be triggered when a tag is created and pushed
+ **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`).
+ **Disable code diffs** - Don't include possibly sensitive code diffs in notification body.
diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md
index 710cf78e84f..543baaa81e1 100644
--- a/doc/user/project/integrations/kubernetes.md
+++ b/doc/user/project/integrations/kubernetes.md
@@ -2,11 +2,15 @@
last_updated: 2017-12-28
---
-CAUTION: **Warning:**
-Kubernetes service integration has been deprecated in GitLab 10.3. If the service is active the cluster information still be editable, however we advised to disable and reconfigure the clusters using the new [Clusters](../clusters/index.md) page. If the service is inactive the fields will be uneditable. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information.
-
# GitLab Kubernetes / OpenShift integration
+CAUTION: **Warning:**
+The Kubernetes service integration has been deprecated in GitLab 10.3. If the
+service is active, the cluster information will still be editable, however we
+advise to disable and reconfigure the clusters using the new
+[Clusters](../clusters/index.md) page. If the service is inactive, the fields
+will not be editable. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information.
+
GitLab can be configured to interact with Kubernetes, or other systems using the
Kubernetes API (such as OpenShift).
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 82175c70e49..19df78f4140 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -316,7 +316,7 @@ X-Gitlab-Event: Issue Hook
Triggered when a new comment is made on commits, merge requests, issues, and code snippets.
The note data will be stored in `object_attributes` (e.g. `note`, `noteable_type`). The
payload will also include information about the target of the comment. For example,
-a comment on a issue will include the specific issue information under the `issue` key.
+a comment on an issue will include the specific issue information under the `issue` key.
Valid target types:
1. `commit`
diff --git a/doc/user/project/issues/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md
index 5cc7ea383ae..cc8988be36b 100644
--- a/doc/user/project/issues/crosslinking_issues.md
+++ b/doc/user/project/issues/crosslinking_issues.md
@@ -40,7 +40,7 @@ issues around that same idea.
You do that as explained above, when
[mentioning an issue from a commit message](#from-commit-messages).
-When mentioning the issue "A" in a issue "B", the issue "A" will also
+When mentioning the issue "A" in issue "B", the issue "A" will also
display a notification in its tracker. The same is valid for mentioning
issues in merge requests.
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 20249926910..27832b0fa2b 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -52,7 +52,7 @@ special options available when filtering by milestone:
The milestone sidebar shows percentage complete, start date and due date,
issues, total issue weight, total issue time spent, and merge requests.
-The percentage complete is calcualted as: Closed and merged merge requests plus all closed issues divided by
+The percentage complete is calculated as: Closed and merged merge requests plus all closed issues divided by
total merge requests and issues.
![Milestone sidebar](img/sidebar.png)
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 17dcd152363..15455a54627 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -91,7 +91,7 @@ to steal the tokens of other jobs.
Since 9.0 [pipeline triggers][triggers] do support the new permission model.
The new triggers do impersonate their associated user including their access
-to projects and their project permissions. To migrate trigger to use new permisison
+to projects and their project permissions. To migrate trigger to use new permission
model use **Take ownership**.
## Before GitLab 8.12
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 3ab88948fbd..f52f66f518a 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -373,7 +373,7 @@ configuration.
If the case of `404.html`, there are different scenarios. For example:
- If you use project Pages (served under `/projectname/`) and try to access
- `/projectname/non/exsiting_file`, GitLab Pages will try to serve first
+ `/projectname/non/existing_file`, GitLab Pages will try to serve first
`/projectname/404.html`, and then `/404.html`.
- If you use user/group Pages (served under `/`) and try to access
`/non/existing_file` GitLab Pages will try to serve `/404.html`.
diff --git a/doc/user/project/pipelines/img/pipeline_schedule_play.png b/doc/user/project/pipelines/img/pipeline_schedule_play.png
new file mode 100644
index 00000000000..f594ceee19d
--- /dev/null
+++ b/doc/user/project/pipelines/img/pipeline_schedule_play.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/pipeline_schedules_list.png b/doc/user/project/pipelines/img/pipeline_schedules_list.png
index 50d9d184b05..2ab2061db94 100644
--- a/doc/user/project/pipelines/img/pipeline_schedules_list.png
+++ b/doc/user/project/pipelines/img/pipeline_schedules_list.png
Binary files differ
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 2101e3b1d58..34809a2826f 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -31,6 +31,20 @@ is installed on.
![Schedules list](img/pipeline_schedules_list.png)
+### Running a scheduled pipeline manually
+
+> [Introduced][ce-15700] in GitLab 10.4.
+
+To trigger a pipeline schedule manually, click the "Play" button:
+
+![Play Pipeline Schedule](img/pipeline_schedule_play.png)
+
+This will schedule a background job to run the pipeline schedule. A flash
+message will provide a link to the CI/CD Pipeline index page.
+
+To help avoid abuse, users are rate limited to triggering a pipeline once per
+minute.
+
### Making use of scheduled pipeline variables
> [Introduced][ce-12328] in GitLab 9.4.
@@ -90,4 +104,5 @@ don't have admin access to the server, ask your administrator.
[ce-10533]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533
[ce-10853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853
[ce-12328]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328
+[ce-15700]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15700
[settings]: https://about.gitlab.com/gitlab-com/settings/#cron-jobs
diff --git a/doc/workflow/lfs/img/lfs-icon.png b/doc/workflow/lfs/img/lfs-icon.png
new file mode 100644
index 00000000000..eef9a14187a
--- /dev/null
+++ b/doc/workflow/lfs/img/lfs-icon.png
Binary files differ
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index f7b87dee8e1..ce7895780c3 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -4,6 +4,11 @@ Managing large files such as audio, video and graphics files has always been one
of the shortcomings of Git. The general recommendation is to not have Git repositories
larger than 1GB to preserve performance.
+![Git LFS tracking status](img/lfs-icon.png)
+
+An LFS icon is shown on files tracked by Git LFS to denote if a file is stored
+as a blob or as an LFS pointer.
+
## How it works
Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 3e2e7d0f7b6..37265a5b771 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -65,7 +65,7 @@ Below is the table of events users can be notified of:
| Group access level changed | User | Sent when user group access level is changed |
| Project moved | Project members [1] | [1] not disabled |
-### Issue / Merge Request events
+### Issue / Merge request events
In all of the below cases, the notification will be sent to:
- Participants:
@@ -104,3 +104,33 @@ You won't receive notifications for Issues, Merge Requests or Milestones
created by yourself. You will only receive automatic notifications when
somebody else comments or adds changes to the ones that you've created or
mentions you.
+
+### Email Headers
+
+Notification emails include headers that provide extra content about the notification received:
+
+| Header | Description |
+|-----------------------------|-------------------------------------------------------------------------|
+| X-GitLab-Project | The name of the project the notification belongs to |
+| X-GitLab-Project-Id | The ID of the project |
+| X-GitLab-Project-Path | The path of the project |
+| X-GitLab-(Resource)-ID | The ID of the resource the notification is for, where resource is `Issue`, `MergeRequest`, `Commit`, etc|
+| X-GitLab-Discussion-ID | Only in comment emails, the ID of the discussion the comment is from |
+| X-GitLab-Pipeline-Id | Only in pipeline emails, the ID of the pipeline the notification is for |
+| X-GitLab-Reply-Key | A unique token to support reply by email |
+| X-GitLab-NotificationReason | The reason for being notified. "mentioned", "assigned", etc |
+
+#### X-GitLab-NotificationReason
+This header holds the reason for the notification to have been sent out,
+where reason can be `mentioned`, `assigned`, `own_activity`, etc.
+Only one reason is sent out according to its priority:
+- `own_activity`
+- `assigned`
+- `mentioned`
+
+The reason in this header will also be shown in the footer of the notification email. For example an email with the
+reason `assigned` will have this sentence in the footer:
+`"You are receiving this email because you have been assigned an item on {configured GitLab hostname}"`
+
+**Note: Only reasons listed above have been implemented so far**
+Further implementation is [being discussed here](https://gitlab.com/gitlab-org/gitlab-ce/issues/42062)
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index d6cfa524a3a..819354bb780 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -164,7 +164,7 @@ Feature: Project Issues
Given project "Shop" have "Release 0.4" open issue
When I visit issue page "Release 0.4"
Then I should see that I am subscribed
- When I click button "Unsubscribe"
+ When I click the subscription toggle
Then I should see that I am unsubscribed
@javascript
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 3843374678c..3cd26bb429b 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -21,20 +21,20 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
step 'I should see that I am subscribed' do
wait_for_requests
- expect(find('.js-issuable-subscribe-button span')).to have_content 'Unsubscribe'
+ expect(find('.js-issuable-subscribe-button')).to have_css 'button.is-checked'
end
step 'I should see that I am unsubscribed' do
wait_for_requests
- expect(find('.js-issuable-subscribe-button span')).to have_content 'Subscribe'
+ expect(find('.js-issuable-subscribe-button')).to have_css 'button:not(.is-checked)'
end
step 'I click link "Closed"' do
find('.issues-state-filters [data-state="closed"] span', text: 'Closed').click
end
- step 'I click button "Unsubscribe"' do
- click_on "Unsubscribe"
+ step 'I click the subscription toggle' do
+ find('.js-issuable-subscribe-button button').click
end
step 'I should see "Release 0.3" in issues' do
diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb
index 598c76f6168..c13154dc0ec 100644
--- a/lib/api/circuit_breakers.rb
+++ b/lib/api/circuit_breakers.rb
@@ -17,11 +17,11 @@ module API
end
def storage_health
- @failing_storage_health ||= Gitlab::Git::Storage::Health.for_all_storages
+ @storage_health ||= Gitlab::Git::Storage::Health.for_all_storages
end
end
- desc 'Get all failing git storages' do
+ desc 'Get all git storages' do
detail 'This feature was introduced in GitLab 9.5'
success Entities::RepositoryStorageHealth
end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 281269b1190..b0b7b50998f 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -4,6 +4,16 @@ module API
before { authenticate! }
+ helpers do
+ def add_deploy_keys_project(project, attrs = {})
+ project.deploy_keys_projects.create(attrs)
+ end
+
+ def find_by_deploy_key(project, key_id)
+ project.deploy_keys_projects.find_by!(deploy_key: key_id)
+ end
+ end
+
desc 'Return all deploy keys'
params do
use :pagination
@@ -21,28 +31,31 @@ module API
before { authorize_admin_project }
desc "Get a specific project's deploy keys" do
- success Entities::SSHKey
+ success Entities::DeployKeysProject
end
params do
use :pagination
end
get ":id/deploy_keys" do
- present paginate(user_project.deploy_keys), with: Entities::SSHKey
+ keys = user_project.deploy_keys_projects.preload(:deploy_key)
+
+ present paginate(keys), with: Entities::DeployKeysProject
end
desc 'Get single deploy key' do
- success Entities::SSHKey
+ success Entities::DeployKeysProject
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
get ":id/deploy_keys/:key_id" do
- key = user_project.deploy_keys.find params[:key_id]
- present key, with: Entities::SSHKey
+ key = find_by_deploy_key(user_project, params[:key_id])
+
+ present key, with: Entities::DeployKeysProject
end
desc 'Add new deploy key to currently authenticated user' do
- success Entities::SSHKey
+ success Entities::DeployKeysProject
end
params do
requires :key, type: String, desc: 'The new deploy key'
@@ -53,24 +66,31 @@ module API
params[:key].strip!
# Check for an existing key joined to this project
- key = user_project.deploy_keys.find_by(key: params[:key])
+ key = user_project.deploy_keys_projects
+ .joins(:deploy_key)
+ .find_by(keys: { key: params[:key] })
+
if key
- present key, with: Entities::SSHKey
+ present key, with: Entities::DeployKeysProject
break
end
# Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: params[:key])
if key
- user_project.deploy_keys << key
- present key, with: Entities::SSHKey
+ added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
+
+ present added_key, with: Entities::DeployKeysProject
break
end
# Create a new deploy key
- key = DeployKey.new(declared_params(include_missing: false))
- if key.valid? && user_project.deploy_keys << key
- present key, with: Entities::SSHKey
+ key_attributes = { can_push: !!params[:can_push],
+ deploy_key_attributes: declared_params.except(:can_push) }
+ key = add_deploy_keys_project(user_project, key_attributes)
+
+ if key.valid?
+ present key, with: Entities::DeployKeysProject
else
render_validation_error!(key)
end
@@ -86,14 +106,21 @@ module API
at_least_one_of :title, :can_push
end
put ":id/deploy_keys/:key_id" do
- key = DeployKey.find(params.delete(:key_id))
+ deploy_keys_project = find_by_deploy_key(user_project, params[:key_id])
- authorize!(:update_deploy_key, key)
+ authorize!(:update_deploy_key, deploy_keys_project.deploy_key)
- if key.update_attributes(declared_params(include_missing: false))
- present key, with: Entities::SSHKey
+ can_push = params[:can_push].nil? ? deploy_keys_project.can_push : params[:can_push]
+ title = params[:title] || deploy_keys_project.deploy_key.title
+
+ result = deploy_keys_project.update_attributes(can_push: can_push,
+ deploy_key_attributes: { id: params[:key_id],
+ title: title })
+
+ if result
+ present deploy_keys_project, with: Entities::DeployKeysProject
else
- render_validation_error!(key)
+ render_validation_error!(deploy_keys_project)
end
end
@@ -122,7 +149,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
delete ":id/deploy_keys/:key_id" do
- key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
+ key = user_project.deploy_keys.find(params[:key_id])
not_found!('Deploy Key') unless key
destroy_conditionally!(key)
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index 1efee9a1324..184fae0eb76 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -15,11 +15,13 @@ module API
end
params do
use :pagination
+ optional :order_by, type: String, values: %w[id iid created_at ref], default: 'id', desc: 'Return deployments ordered by `id` or `iid` or `created_at` or `ref`'
+ optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
end
get ':id/deployments' do
authorize! :read_deployment, user_project
- present paginate(user_project.deployments), with: Entities::Deployment
+ present paginate(user_project.deployments.order(params[:order_by] => params[:sort])), with: Entities::Deployment
end
desc 'Gets a specific deployment' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c4ef2c74658..3f4b62dc1b2 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -65,12 +65,12 @@ module API
end
class Hook < Grape::Entity
- expose :id, :url, :created_at, :push_events, :tag_push_events, :repository_update_events
+ expose :id, :url, :created_at, :push_events, :tag_push_events, :merge_requests_events, :repository_update_events
expose :enable_ssl_verification
end
class ProjectHook < Hook
- expose :project_id, :issues_events, :merge_requests_events
+ expose :project_id, :issues_events
expose :note_events, :pipeline_events, :wiki_page_events
expose :job_events
end
@@ -554,13 +554,18 @@ module API
end
class SSHKey < Grape::Entity
- expose :id, :title, :key, :created_at, :can_push
+ expose :id, :title, :key, :created_at
end
class SSHKeyWithUser < SSHKey
expose :user, using: Entities::UserPublic
end
+ class DeployKeysProject < Grape::Entity
+ expose :deploy_key, merge: true, using: Entities::SSHKey
+ expose :can_push
+ end
+
class GPGKey < Grape::Entity
expose :id, :key, :created_at
end
@@ -714,10 +719,7 @@ module API
expose :job_events
# Expose serialized properties
expose :properties do |service, options|
- field_names = service.fields
- .select { |field| options[:include_passwords] || field[:type] != 'password' }
- .map { |field| field[:name] }
- service.properties.slice(*field_names)
+ service.properties.slice(*service.api_field_names)
end
end
diff --git a/lib/api/helpers/common_helpers.rb b/lib/api/helpers/common_helpers.rb
index 322624c6092..9993caa5249 100644
--- a/lib/api/helpers/common_helpers.rb
+++ b/lib/api/helpers/common_helpers.rb
@@ -3,8 +3,10 @@ module API
module CommonHelpers
def convert_parameters_from_legacy_format(params)
params.tap do |params|
- if params[:assignee_id].present?
- params[:assignee_ids] = [params.delete(:assignee_id)]
+ assignee_id = params.delete(:assignee_id)
+
+ if assignee_id.present?
+ params[:assignee_ids] = [assignee_id]
end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index eff1c5b70ea..eb67de81a0d 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -1,11 +1,6 @@
module API
module Helpers
module InternalHelpers
- SSH_GITALY_FEATURES = {
- 'git-receive-pack' => [:ssh_receive_pack, Gitlab::GitalyClient::MigrationStatus::OPT_IN],
- 'git-upload-pack' => [:ssh_upload_pack, Gitlab::GitalyClient::MigrationStatus::OPT_OUT]
- }.freeze
-
attr_reader :redirected_path
def wiki?
@@ -102,8 +97,14 @@ module API
# Return the Gitaly Address if it is enabled
def gitaly_payload(action)
- feature, status = SSH_GITALY_FEATURES[action]
- return unless feature && Gitlab::GitalyClient.feature_enabled?(feature, status: status)
+ return unless %w[git-receive-pack git-upload-pack].include?(action)
+
+ if action == 'git-receive-pack'
+ return unless Gitlab::GitalyClient.feature_enabled?(
+ :ssh_receive_pack,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT
+ )
+ end
{
repository: repository.gitaly_repository,
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index a116ab3c9bd..9c205514b3a 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -38,6 +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)
present paginate(builds), with: Entities::Job
end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 5446f6b54b1..130c6d6da71 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -22,7 +22,7 @@ module API
source = find_source(source_type, params[:id])
users = source.users
- users = users.merge(User.search(params[:query])) if params[:query]
+ users = users.merge(User.search(params[:query])) if params[:query].present?
present paginate(users), with: Entities::Member, source: source
end
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index 0cb209a02d0..306dc0e63d7 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -60,6 +60,15 @@ module API
update_milestone_for(user_project)
end
+ desc 'Remove a project milestone'
+ delete ":id/milestones/:milestone_id" do
+ authorize! :admin_milestone, user_project
+
+ user_project.milestones.find(params[:milestone_id]).destroy
+
+ status(204)
+ end
+
desc 'Get all issues for a single project milestone' do
success Entities::IssueBasic
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 5bed58c2d63..39c03c40bab 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -143,7 +143,7 @@ module API
get ":id/snippets/:snippet_id/user_agent_detail" do
authenticated_as_admin!
- snippet = Snippet.find_by!(id: params[:id])
+ snippet = Snippet.find_by!(id: params[:snippet_id], project_id: params[:id])
return not_found!('UserAgentDetail') unless snippet.user_agent_detail
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 653126e79ea..8b5e4f8edcc 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -76,9 +76,9 @@ module API
def present_projects(projects, options = {})
projects = reorder_projects(projects)
- projects = projects.with_statistics if params[:statistics]
- projects = projects.with_issues_enabled if params[:with_issues_enabled]
+ projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
+ projects = projects.with_statistics if params[:statistics]
projects = paginate(projects)
if current_user
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index 614822509f0..c15c487deb4 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -2,7 +2,7 @@ module API
class ProtectedBranches < Grape::API
include PaginationParams
- BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
+ BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(name: API::NO_SLASH_URL_PART_REGEX)
before { authorize_admin_project }
diff --git a/lib/api/services.rb b/lib/api/services.rb
index a7f44e2869c..51e33e2c686 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -785,7 +785,7 @@ module API
service_params = declared_params(include_missing: false).merge(active: true)
if service.update_attributes(service_params)
- present service, with: Entities::ProjectService, include_passwords: current_user.admin?
+ present service, with: Entities::ProjectService
else
render_api_error!('400 Bad Request', 400)
end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 6b6a03e3300..c7a460df46a 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -26,6 +26,7 @@ module API
optional :token, type: String, desc: 'The token used to validate payloads'
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
+ optional :merge_requests_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
end
post do
diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb
index fa0bef39602..ac76fece931 100644
--- a/lib/api/v3/builds.rb
+++ b/lib/api/v3/builds.rb
@@ -36,6 +36,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)
present paginate(builds), with: ::API::V3::Entities::Build
end
diff --git a/lib/api/v3/deploy_keys.rb b/lib/api/v3/deploy_keys.rb
index b90e2061da3..47e54ca85a5 100644
--- a/lib/api/v3/deploy_keys.rb
+++ b/lib/api/v3/deploy_keys.rb
@@ -3,6 +3,16 @@ module API
class DeployKeys < Grape::API
before { authenticate! }
+ helpers do
+ def add_deploy_keys_project(project, attrs = {})
+ project.deploy_keys_projects.create(attrs)
+ end
+
+ def find_by_deploy_key(project, key_id)
+ project.deploy_keys_projects.find_by!(deploy_key: key_id)
+ end
+ end
+
get "deploy_keys" do
authenticated_as_admin!
@@ -18,25 +28,28 @@ module API
%w(keys deploy_keys).each do |path|
desc "Get a specific project's deploy keys" do
- success ::API::Entities::SSHKey
+ success ::API::Entities::DeployKeysProject
end
get ":id/#{path}" do
- present user_project.deploy_keys, with: ::API::Entities::SSHKey
+ keys = user_project.deploy_keys_projects.preload(:deploy_key)
+
+ present keys, with: ::API::Entities::DeployKeysProject
end
desc 'Get single deploy key' do
- success ::API::Entities::SSHKey
+ success ::API::Entities::DeployKeysProject
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
get ":id/#{path}/:key_id" do
- key = user_project.deploy_keys.find params[:key_id]
- present key, with: ::API::Entities::SSHKey
+ key = find_by_deploy_key(user_project, params[:key_id])
+
+ present key, with: ::API::Entities::DeployKeysProject
end
desc 'Add new deploy key to currently authenticated user' do
- success ::API::Entities::SSHKey
+ success ::API::Entities::DeployKeysProject
end
params do
requires :key, type: String, desc: 'The new deploy key'
@@ -47,24 +60,31 @@ module API
params[:key].strip!
# Check for an existing key joined to this project
- key = user_project.deploy_keys.find_by(key: params[:key])
+ key = user_project.deploy_keys_projects
+ .joins(:deploy_key)
+ .find_by(keys: { key: params[:key] })
+
if key
- present key, with: ::API::Entities::SSHKey
+ present key, with: ::API::Entities::DeployKeysProject
break
end
# Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: params[:key])
if key
- user_project.deploy_keys << key
- present key, with: ::API::Entities::SSHKey
+ added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
+
+ present added_key, with: ::API::Entities::DeployKeysProject
break
end
# Create a new deploy key
- key = DeployKey.new(declared_params(include_missing: false))
- if key.valid? && user_project.deploy_keys << key
- present key, with: ::API::Entities::SSHKey
+ key_attributes = { can_push: !!params[:can_push],
+ deploy_key_attributes: declared_params.except(:can_push) }
+ key = add_deploy_keys_project(user_project, key_attributes)
+
+ if key.valid?
+ present key, with: ::API::Entities::DeployKeysProject
else
render_validation_error!(key)
end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 64758dae7d3..2ccbb9da1c5 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -257,10 +257,7 @@ module API
expose :job_events, as: :build_events
# Expose serialized properties
expose :properties do |service, options|
- field_names = service.fields
- .select { |field| options[:include_passwords] || field[:type] != 'password' }
- .map { |field| field[:name] }
- service.properties.slice(*field_names)
+ service.properties.slice(*service.api_field_names)
end
end
diff --git a/lib/api/v3/members.rb b/lib/api/v3/members.rb
index de226e4e573..46145cac7a5 100644
--- a/lib/api/v3/members.rb
+++ b/lib/api/v3/members.rb
@@ -23,7 +23,7 @@ module API
source = find_source(source_type, params[:id])
users = source.users
- users = users.merge(User.search(params[:query])) if params[:query]
+ users = users.merge(User.search(params[:query])) if params[:query].present?
present paginate(users), with: ::API::Entities::Member, source: source
end
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index 44ed94d2869..20ca1021c71 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -622,7 +622,7 @@ module API
end
get ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
- present service, with: Entities::ProjectService, include_passwords: current_user.admin?
+ present service, with: Entities::ProjectService
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 05aa79dc160..f27ce4d2b2b 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -108,7 +108,10 @@ module Backup
$progress.puts "Please make sure that file name ends with #{FILE_NAME_SUFFIX}"
exit 1
elsif backup_file_list.many? && ENV["BACKUP"].nil?
- $progress.puts 'Found more than one backup, please specify which one you want to restore:'
+ $progress.puts 'Found more than one backup:'
+ # print list of available backups
+ $progress.puts " " + available_timestamps.join("\n ")
+ $progress.puts 'Please specify which one you want to restore:'
$progress.puts 'rake gitlab:backup:restore BACKUP=timestamp_of_backup'
exit 1
end
@@ -169,6 +172,10 @@ module Backup
@backup_file_list ||= Dir.glob("*#{FILE_NAME_SUFFIX}")
end
+ def available_timestamps
+ @backup_file_list.map {|item| item.gsub("#{FILE_NAME_SUFFIX}", "")}
+ end
+
def connect_to_remote_directory(connection_settings)
# our settings use string keys, but Fog expects symbols
connection = ::Fog::Storage.new(connection_settings.symbolize_keys)
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 5c197afd782..9bdedeb6615 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -50,15 +50,22 @@ module Banzai
end
def process_link_to_upload_attr(html_attr)
- uri_parts = [html_attr.value]
+ path_parts = [Addressable::URI.unescape(html_attr.value)]
if group
- uri_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
+ path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
elsif project
- uri_parts.unshift(relative_url_root, project.full_path)
+ path_parts.unshift(relative_url_root, project.full_path)
end
- html_attr.value = File.join(*uri_parts)
+ path = Addressable::URI.escape(File.join(*path_parts))
+
+ html_attr.value =
+ if context[:only_path]
+ path
+ else
+ Addressable::URI.join(Gitlab.config.gitlab.base_url, path).to_s
+ end
end
def process_link_to_repository_attr(html_attr)
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
index e7a1ec8457d..072d24e5a11 100644
--- a/lib/banzai/filter/wiki_link_filter/rewriter.rb
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -9,6 +9,10 @@ module Banzai
end
def apply_rules
+ # Special case: relative URLs beginning with `/uploads/` refer to
+ # user-uploaded files and will be handled elsewhere.
+ return @uri.to_s if @uri.relative? && @uri.path.starts_with?('/uploads/')
+
apply_file_link_rules!
apply_hierarchical_link_rules!
apply_relative_link_rules!
diff --git a/lib/gitlab/auth/blocked_user_tracker.rb b/lib/gitlab/auth/blocked_user_tracker.rb
new file mode 100644
index 00000000000..dae03a179e4
--- /dev/null
+++ b/lib/gitlab/auth/blocked_user_tracker.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+module Gitlab
+ module Auth
+ class BlockedUserTracker
+ ACTIVE_RECORD_REQUEST_PARAMS = 'action_dispatch.request.request_parameters'
+
+ def self.log_if_user_blocked(env)
+ message = env.dig('warden.options', :message)
+
+ # Devise calls User#active_for_authentication? on the User model and then
+ # throws an exception to Warden with User#inactive_message:
+ # https://github.com/plataformatec/devise/blob/v4.2.1/lib/devise/hooks/activatable.rb#L8
+ #
+ # Since Warden doesn't pass the user record to the failure handler, we
+ # need to do a database lookup with the username. We can limit the
+ # lookups to happen when the user was blocked by checking the inactive
+ # message passed along by Warden.
+ return unless message == User::BLOCKED_MESSAGE
+
+ login = env.dig(ACTIVE_RECORD_REQUEST_PARAMS, 'user', 'login')
+
+ return unless login.present?
+
+ user = User.by_login(login)
+
+ return unless user&.blocked?
+
+ Gitlab::AppLogger.info("Failed login for blocked user: user=#{user.username} ip=#{env['REMOTE_ADDR']}")
+ SystemHooksService.new.execute_hooks_for(user, :failed_login)
+
+ true
+ rescue TypeError
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb
index b4114a3ac96..cf02030c577 100644
--- a/lib/gitlab/auth/user_auth_finders.rb
+++ b/lib/gitlab/auth/user_auth_finders.rb
@@ -96,9 +96,7 @@ module Gitlab
end
def ensure_action_dispatch_request(request)
- return request if request.is_a?(ActionDispatch::Request)
-
- ActionDispatch::Request.new(request.env)
+ ActionDispatch::Request.new(request.env.dup)
end
def current_request
diff --git a/lib/gitlab/background_migration/copy_column.rb b/lib/gitlab/background_migration/copy_column.rb
index a2cb215c230..ef70f37d5eb 100644
--- a/lib/gitlab/background_migration/copy_column.rb
+++ b/lib/gitlab/background_migration/copy_column.rb
@@ -28,6 +28,8 @@ module Gitlab
UPDATE #{quoted_table}
SET #{quoted_copy_to} = #{quoted_copy_from}
WHERE id BETWEEN #{start_id} AND #{end_id}
+ AND #{quoted_copy_from} IS NOT NULL
+ AND #{quoted_copy_to} IS NULL
SQL
end
diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
index 476c46341ae..4e0121ca34d 100644
--- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -7,6 +7,7 @@ module Gitlab
class PrepareUntrackedUploads # rubocop:disable Metrics/ClassLength
# For bulk_queue_background_migration_jobs_by_range
include Database::MigrationHelpers
+ include ::Gitlab::Utils::StrongMemoize
FIND_BATCH_SIZE = 500
RELATIVE_UPLOAD_DIR = "uploads".freeze
@@ -142,7 +143,9 @@ module Gitlab
end
def postgresql?
- @postgresql ||= Gitlab::Database.postgresql?
+ strong_memoize(:postgresql) do
+ Gitlab::Database.postgresql?
+ end
end
def can_bulk_insert_and_ignore_duplicates?
@@ -150,8 +153,9 @@ module Gitlab
end
def postgresql_pre_9_5?
- @postgresql_pre_9_5 ||= postgresql? &&
- Gitlab::Database.version.to_f < 9.5
+ strong_memoize(:postgresql_pre_9_5) do
+ postgresql? && Gitlab::Database.version.to_f < 9.5
+ end
end
def schedule_populate_untracked_uploads_jobs
diff --git a/lib/gitlab/bare_repository_import/repository.rb b/lib/gitlab/bare_repository_import/repository.rb
index 85b79362196..c0c666dfb7b 100644
--- a/lib/gitlab/bare_repository_import/repository.rb
+++ b/lib/gitlab/bare_repository_import/repository.rb
@@ -1,6 +1,8 @@
module Gitlab
module BareRepositoryImport
class Repository
+ include ::Gitlab::Utils::StrongMemoize
+
attr_reader :group_path, :project_name, :repo_path
def initialize(root_path, repo_path)
@@ -41,11 +43,15 @@ module Gitlab
private
def wiki?
- @wiki ||= repo_path.end_with?('.wiki.git')
+ strong_memoize(:wiki) do
+ repo_path.end_with?('.wiki.git')
+ end
end
def hashed?
- @hashed ||= repo_relative_path.include?('@hashed')
+ strong_memoize(:hashed) do
+ repo_relative_path.include?('@hashed')
+ end
end
def repo_relative_path
diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb
index eb606b57667..55658900628 100644
--- a/lib/gitlab/ci/config/entry/validators.rb
+++ b/lib/gitlab/ci/config/entry/validators.rb
@@ -64,10 +64,24 @@ module Gitlab
include LegacyValidationHelpers
def validate_each(record, attribute, value)
- unless validate_string(value)
+ if validate_string(value)
+ validate_path(record, attribute, value)
+ else
record.errors.add(attribute, 'should be a string or symbol')
end
end
+
+ private
+
+ def validate_path(record, attribute, value)
+ path = CGI.unescape(value.to_s)
+
+ if path.include?('/')
+ record.errors.add(attribute, 'cannot contain the "/" character')
+ elsif path == '.' || path == '..'
+ record.errors.add(attribute, 'cannot be "." or ".."')
+ end
+ end
end
class RegexpValidator < ActiveModel::EachValidator
diff --git a/lib/gitlab/ci/pipeline/chain/skip.rb b/lib/gitlab/ci/pipeline/chain/skip.rb
index 9a72de87bab..32cbb7ca6af 100644
--- a/lib/gitlab/ci/pipeline/chain/skip.rb
+++ b/lib/gitlab/ci/pipeline/chain/skip.rb
@@ -3,6 +3,8 @@ module Gitlab
module Pipeline
module Chain
class Skip < Chain::Base
+ include ::Gitlab::Utils::StrongMemoize
+
SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i
def perform!
@@ -24,7 +26,9 @@ module Gitlab
def commit_message_skips_ci?
return false unless @pipeline.git_commit_message
- @skipped ||= !!(@pipeline.git_commit_message =~ SKIP_PATTERN)
+ strong_memoize(:commit_message_skips_ci) do
+ !!(@pipeline.git_commit_message =~ SKIP_PATTERN)
+ end
end
end
end
diff --git a/lib/gitlab/ci/stage/seed.rb b/lib/gitlab/ci/stage/seed.rb
index bc97aa63b02..f33c87f554d 100644
--- a/lib/gitlab/ci/stage/seed.rb
+++ b/lib/gitlab/ci/stage/seed.rb
@@ -2,6 +2,8 @@ module Gitlab
module Ci
module Stage
class Seed
+ include ::Gitlab::Utils::StrongMemoize
+
attr_reader :pipeline
delegate :project, to: :pipeline
@@ -50,7 +52,9 @@ module Gitlab
private
def protected_ref?
- @protected_ref ||= project.protected_for?(pipeline.ref)
+ strong_memoize(:protected_ref) do
+ project.protected_for?(pipeline.ref)
+ end
end
end
end
diff --git a/lib/gitlab/ci/status/build/action.rb b/lib/gitlab/ci/status/build/action.rb
index 45fd0d4aa07..6c9125647ad 100644
--- a/lib/gitlab/ci/status/build/action.rb
+++ b/lib/gitlab/ci/status/build/action.rb
@@ -2,6 +2,9 @@ module Gitlab
module Ci
module Status
module Build
+ ##
+ # Extended status for playable manual actions.
+ #
class Action < Status::Extended
def label
if has_action?
@@ -12,7 +15,7 @@ module Gitlab
end
def self.matches?(build, user)
- build.action?
+ build.playable?
end
end
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 592a1956ceb..dbe6259fce7 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -525,8 +525,9 @@ module Gitlab
install_rename_triggers(table, column, temp_column)
# Schedule the jobs that will copy the data from the old column to the
- # new one.
- relation.each_batch(of: batch_size) do |batch, index|
+ # new one. Rows with NULL values in our source column are skipped since
+ # the target column is already NULL at this point.
+ relation.where.not(column => nil).each_batch(of: batch_size) do |batch, index|
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
max_index = index
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 031fccba92b..f421bf69e8f 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -132,6 +132,8 @@ module Gitlab
end
def find_by_gitaly(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
+ return unless path
+
path = path.sub(/\A\/*/, '')
path = '/' if path.empty?
name = File.basename(path)
@@ -173,6 +175,8 @@ module Gitlab
end
def find_by_rugged(repository, sha, path, limit:)
+ return unless path
+
rugged_commit = repository.lookup(sha)
root_tree = rugged_commit.tree
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 016437b2419..768617e2cae 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -239,6 +239,24 @@ module Gitlab
end
end
end
+
+ def extract_signature(repository, commit_id)
+ repository.gitaly_migrate(:extract_commit_signature) do |is_enabled|
+ if is_enabled
+ repository.gitaly_commit_client.extract_signature(commit_id)
+ else
+ rugged_extract_signature(repository, commit_id)
+ end
+ end
+ end
+
+ def rugged_extract_signature(repository, commit_id)
+ begin
+ Rugged::Commit.extract_signature(repository.rugged, commit_id)
+ rescue Rugged::OdbError
+ nil
+ end
+ end
end
def initialize(repository, raw_commit, head = nil)
@@ -436,6 +454,16 @@ module Gitlab
parent_ids.size > 1
end
+ def tree_entry(path)
+ @repository.gitaly_migrate(:commit_tree_entry) do |is_migrated|
+ if is_migrated
+ gitaly_tree_entry(path)
+ else
+ rugged_tree_entry(path)
+ end
+ end
+ end
+
def to_gitaly_commit
return raw_commit if raw_commit.is_a?(Gitaly::GitCommit)
@@ -450,11 +478,6 @@ module Gitlab
)
end
- # Is this the same as Blob.find_entry_by_path ?
- def rugged_tree_entry(path)
- rugged_commit.tree.path(path)
- end
-
private
def init_from_hash(hash)
@@ -501,6 +524,28 @@ module Gitlab
SERIALIZE_KEYS
end
+ def gitaly_tree_entry(path)
+ # We're only interested in metadata, so limit actual data to 1 byte
+ # since Gitaly doesn't support "send no data" option.
+ entry = @repository.gitaly_commit_client.tree_entry(id, path, 1)
+ return unless entry
+
+ # To be compatible with the rugged format
+ entry = entry.to_h
+ entry.delete(:data)
+ entry[:name] = File.basename(path)
+ entry[:type] = entry[:type].downcase
+
+ entry
+ end
+
+ # Is this the same as Blob.find_entry_by_path ?
+ def rugged_tree_entry(path)
+ rugged_commit.tree.path(path)
+ rescue Rugged::TreeError
+ nil
+ end
+
def gitaly_commit_author_from_rugged(author_or_committer)
Gitaly::CommitAuthor.new(
name: author_or_committer[:name].b,
diff --git a/lib/gitlab/git/conflict/resolver.rb b/lib/gitlab/git/conflict/resolver.rb
index 74c9874d590..07b7e811a34 100644
--- a/lib/gitlab/git/conflict/resolver.rb
+++ b/lib/gitlab/git/conflict/resolver.rb
@@ -15,7 +15,7 @@ module Gitlab
@conflicts ||= begin
@target_repository.gitaly_migrate(:conflicts_list_conflict_files) do |is_enabled|
if is_enabled
- gitaly_conflicts_client(@target_repository).list_conflict_files
+ gitaly_conflicts_client(@target_repository).list_conflict_files.to_a
else
rugged_list_conflict_files
end
diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb
index 976fa1ddfe6..e5a747cb987 100644
--- a/lib/gitlab/git/gitlab_projects.rb
+++ b/lib/gitlab/git/gitlab_projects.rb
@@ -44,29 +44,13 @@ module Gitlab
# Import project via git clone --bare
# URL must be publicly cloneable
def import_project(source, timeout)
- # Skip import if repo already exists
- return false if File.exist?(repository_absolute_path)
-
- masked_source = mask_password_in_url(source)
-
- logger.info "Importing project from <#{masked_source}> to <#{repository_absolute_path}>."
- cmd = %W(git clone --bare -- #{source} #{repository_absolute_path})
-
- success = run_with_timeout(cmd, timeout, nil)
-
- unless success
- logger.error("Importing project from <#{masked_source}> to <#{repository_absolute_path}> failed.")
- FileUtils.rm_rf(repository_absolute_path)
- return false
+ Gitlab::GitalyClient.migrate(:import_repository) do |is_enabled|
+ if is_enabled
+ gitaly_import_repository(source)
+ else
+ git_import_repository(source, timeout)
+ end
end
-
- Gitlab::Git::Repository.create_hooks(repository_absolute_path, global_hooks_path)
-
- # The project was imported successfully.
- # Remove the origin URL since it may contain password.
- remove_origin_in_repo
-
- true
end
def fork_repository(new_shard_path, new_repository_relative_path)
@@ -231,6 +215,42 @@ module Gitlab
raise(ShardNameNotFoundError, "no shard found for path '#{shard_path}'")
end
+ def git_import_repository(source, timeout)
+ # Skip import if repo already exists
+ return false if File.exist?(repository_absolute_path)
+
+ masked_source = mask_password_in_url(source)
+
+ logger.info "Importing project from <#{masked_source}> to <#{repository_absolute_path}>."
+ cmd = %W(git clone --bare -- #{source} #{repository_absolute_path})
+
+ success = run_with_timeout(cmd, timeout, nil)
+
+ unless success
+ logger.error("Importing project from <#{masked_source}> to <#{repository_absolute_path}> failed.")
+ FileUtils.rm_rf(repository_absolute_path)
+ return false
+ end
+
+ Gitlab::Git::Repository.create_hooks(repository_absolute_path, global_hooks_path)
+
+ # The project was imported successfully.
+ # Remove the origin URL since it may contain password.
+ remove_origin_in_repo
+
+ true
+ end
+
+ def gitaly_import_repository(source)
+ raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil)
+
+ Gitlab::GitalyClient::RepositoryService.new(raw_repository).import_repository(source)
+ true
+ rescue GRPC::BadStatus => e
+ @output << e.message
+ false
+ end
+
def git_fork_repository(new_shard_path, new_repository_relative_path)
from_path = repository_absolute_path
to_path = File.join(new_shard_path, new_repository_relative_path)
diff --git a/lib/gitlab/git/ref.rb b/lib/gitlab/git/ref.rb
index 372ce005b94..a3ba9475ad0 100644
--- a/lib/gitlab/git/ref.rb
+++ b/lib/gitlab/git/ref.rb
@@ -33,9 +33,9 @@ module Gitlab
object
end
- def initialize(repository, name, target, derefenced_target)
+ def initialize(repository, name, target, dereferenced_target)
@name = Gitlab::Git.ref_name(name)
- @dereferenced_target = derefenced_target
+ @dereferenced_target = dereferenced_target
@target = if target.respond_to?(:oid)
target.oid
elsif target.respond_to?(:name)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 6ada9a145db..8bfc3fe52c5 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -490,11 +490,7 @@ module Gitlab
return []
end
- if log_using_shell?(options)
- log_by_shell(sha, options)
- else
- log_by_walk(sha, options)
- end
+ log_by_shell(sha, options)
end
def count_commits(options)
@@ -621,37 +617,6 @@ module Gitlab
end
end
- # Returns branch names collection that contains the special commit(SHA1
- # or name)
- #
- # Ex.
- # repo.branch_names_contains('master')
- #
- def branch_names_contains(commit)
- branches_contains(commit).map { |c| c.name }
- end
-
- # Returns branch collection that contains the special commit(SHA1 or name)
- #
- # Ex.
- # repo.branch_names_contains('master')
- #
- def branches_contains(commit)
- commit_obj = rugged.rev_parse(commit)
- parent = commit_obj.parents.first unless commit_obj.parents.empty?
-
- walker = Rugged::Walker.new(rugged)
-
- rugged.branches.select do |branch|
- walker.push(branch.target_id)
- walker.hide(parent) if parent
- result = walker.any? { |c| c.oid == commit_obj.oid }
- walker.reset
-
- result
- end
- end
-
# Get refs hash which key is SHA1
# and value is a Rugged::Reference
def refs_hash
@@ -1126,19 +1091,6 @@ module Gitlab
end
end
- def shell_write_ref(ref_path, ref, old_ref)
- raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
- raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
- raise ArgumentError, "invalid old_ref #{old_ref.inspect}" if !old_ref.nil? && old_ref.include?("\x00")
-
- input = "update #{ref_path}\x00#{ref}\x00#{old_ref}\x00"
- run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) }
- end
-
- def rugged_write_ref(ref_path, ref)
- rugged.references.create(ref_path, ref, force: true)
- end
-
def fetch_ref(source_repository, source_ref:, target_ref:)
Gitlab::Git.check_namespace!(source_repository)
source_repository = RemoteRepository.new(source_repository) unless source_repository.is_a?(RemoteRepository)
@@ -1236,33 +1188,31 @@ module Gitlab
end
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
- rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id)
- env = git_env_for_user(user)
-
- if remote_repository.is_a?(RemoteRepository)
- env.merge!(remote_repository.fetch_env)
- remote_repo_path = GITALY_INTERNAL_URL
- else
- remote_repo_path = remote_repository.path
- end
-
- with_worktree(rebase_path, branch, env: env) do
- run_git!(
- %W(pull --rebase #{remote_repo_path} #{remote_branch}),
- chdir: rebase_path, env: env
- )
-
- rebase_sha = run_git!(%w(rev-parse HEAD), chdir: rebase_path, env: env).strip
-
- Gitlab::Git::OperationService.new(user, self)
- .update_branch(branch, rebase_sha, branch_sha)
-
- rebase_sha
+ gitaly_migrate(:rebase) do |is_enabled|
+ if is_enabled
+ gitaly_rebase(user, rebase_id,
+ branch: branch,
+ branch_sha: branch_sha,
+ remote_repository: remote_repository,
+ remote_branch: remote_branch)
+ else
+ git_rebase(user, rebase_id,
+ branch: branch,
+ branch_sha: branch_sha,
+ remote_repository: remote_repository,
+ remote_branch: remote_branch)
+ end
end
end
def rebase_in_progress?(rebase_id)
- fresh_worktree?(worktree_path(REBASE_WORKTREE_PREFIX, rebase_id))
+ gitaly_migrate(:rebase_in_progress) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.rebase_in_progress?(rebase_id)
+ else
+ fresh_worktree?(worktree_path(REBASE_WORKTREE_PREFIX, rebase_id))
+ end
+ end
end
def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:)
@@ -1354,6 +1304,10 @@ module Gitlab
end
# rubocop:enable Metrics/ParameterLists
+ def write_config(full_path:)
+ rugged.config['gitlab.fullpath'] = full_path if full_path.present?
+ end
+
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
end
@@ -1398,6 +1352,25 @@ module Gitlab
private
+ def shell_write_ref(ref_path, ref, old_ref)
+ raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
+ raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
+ raise ArgumentError, "invalid old_ref #{old_ref.inspect}" if !old_ref.nil? && old_ref.include?("\x00")
+
+ input = "update #{ref_path}\x00#{ref}\x00#{old_ref}\x00"
+ run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) }
+ end
+
+ def rugged_write_ref(ref_path, ref)
+ rugged.references.create(ref_path, ref, force: true)
+ rescue Rugged::ReferenceError => ex
+ Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}"
+ rescue Rugged::OSError => ex
+ raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
+
+ Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}"
+ end
+
def fresh_worktree?(path)
File.exist?(path) && !clean_stuck_worktree(path)
end
@@ -1545,27 +1518,6 @@ module Gitlab
end
end
- def log_using_shell?(options)
- options[:path].present? ||
- options[:disable_walk] ||
- options[:skip_merges] ||
- options[:after] ||
- options[:before]
- end
-
- def log_by_walk(sha, options)
- walk_options = {
- show: sha,
- sort: Rugged::SORT_NONE,
- limit: options[:limit],
- offset: options[:offset]
- }
- Rugged::Walker.walk(rugged, walk_options).to_a
- end
-
- # Gitaly note: JV: although #log_by_shell shells out to Git I think the
- # complexity is such that we should migrate it as Ruby before trying to
- # do it in Go.
def log_by_shell(sha, options)
limit = options[:limit].to_i
offset = options[:offset].to_i
@@ -2039,6 +1991,40 @@ module Gitlab
tree_id
end
+ def gitaly_rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
+ gitaly_operation_client.user_rebase(user, rebase_id,
+ branch: branch,
+ branch_sha: branch_sha,
+ remote_repository: remote_repository,
+ remote_branch: remote_branch)
+ end
+
+ def git_rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
+ rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id)
+ env = git_env_for_user(user)
+
+ if remote_repository.is_a?(RemoteRepository)
+ env.merge!(remote_repository.fetch_env)
+ remote_repo_path = GITALY_INTERNAL_URL
+ else
+ remote_repo_path = remote_repository.path
+ end
+
+ with_worktree(rebase_path, branch, env: env) do
+ run_git!(
+ %W(pull --rebase #{remote_repo_path} #{remote_branch}),
+ chdir: rebase_path, env: env
+ )
+
+ rebase_sha = run_git!(%w(rev-parse HEAD), chdir: rebase_path, env: env).strip
+
+ Gitlab::Git::OperationService.new(user, self)
+ .update_branch(branch, rebase_sha, branch_sha)
+
+ rebase_sha
+ end
+ end
+
def local_fetch_ref(source_path, source_ref:, target_ref:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args)
diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb
index 4974205b8fd..f8b2e7e0e21 100644
--- a/lib/gitlab/git/rev_list.rb
+++ b/lib/gitlab/git/rev_list.rb
@@ -95,7 +95,7 @@ module Gitlab
object_output.map do |output_line|
sha, path = output_line.split(' ', 2)
- next if require_path && path.blank?
+ next if require_path && path.to_s.empty?
sha
end.reject(&:nil?)
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index fed05bb6c64..2231371cfa7 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -177,7 +177,7 @@ module Gitlab
response = GitalyClient.call(@repository.storage, :commit_service, :list_commits_by_oid, request, timeout: GitalyClient.medium_timeout)
consume_commits_response(response)
- rescue GRPC::Unknown # If no repository is found, happens mainly during testing
+ rescue GRPC::NotFound # If no repository is found, happens mainly during testing
[]
end
@@ -282,6 +282,23 @@ module Gitlab
end
end
+ def extract_signature(commit_id)
+ request = Gitaly::ExtractCommitSignatureRequest.new(repository: @gitaly_repo, commit_id: commit_id)
+ response = GitalyClient.call(@repository.storage, :commit_service, :extract_commit_signature, request)
+
+ signature = ''.b
+ signed_text = ''.b
+
+ response.each do |message|
+ signature << message.signature
+ signed_text << message.signed_text
+ end
+
+ return if signature.blank? && signed_text.blank?
+
+ [signature, signed_text]
+ end
+
private
def call_commit_diff(request_params, options = {})
diff --git a/lib/gitlab/gitaly_client/conflict_files_stitcher.rb b/lib/gitlab/gitaly_client/conflict_files_stitcher.rb
new file mode 100644
index 00000000000..97c13d1fdb0
--- /dev/null
+++ b/lib/gitlab/gitaly_client/conflict_files_stitcher.rb
@@ -0,0 +1,47 @@
+module Gitlab
+ module GitalyClient
+ class ConflictFilesStitcher
+ include Enumerable
+
+ def initialize(rpc_response)
+ @rpc_response = rpc_response
+ end
+
+ def each
+ current_file = nil
+
+ @rpc_response.each do |msg|
+ msg.files.each do |gitaly_file|
+ if gitaly_file.header
+ yield current_file if current_file
+
+ current_file = file_from_gitaly_header(gitaly_file.header)
+ else
+ current_file.content << gitaly_file.content
+ end
+ end
+ end
+
+ yield current_file if current_file
+ end
+
+ private
+
+ def file_from_gitaly_header(header)
+ Gitlab::Git::Conflict::File.new(
+ Gitlab::GitalyClient::Util.git_repository(header.repository),
+ header.commit_oid,
+ conflict_from_gitaly_file_header(header),
+ ''
+ )
+ end
+
+ def conflict_from_gitaly_file_header(header)
+ {
+ ours: { path: header.our_path, mode: header.our_mode },
+ theirs: { path: header.their_path }
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb
index 40f032cf873..2565d537aff 100644
--- a/lib/gitlab/gitaly_client/conflicts_service.rb
+++ b/lib/gitlab/gitaly_client/conflicts_service.rb
@@ -20,7 +20,11 @@ module Gitlab
)
response = GitalyClient.call(@repository.storage, :conflicts_service, :list_conflict_files, request)
- files_from_response(response).to_a
+ GitalyClient::ConflictFilesStitcher.new(response)
+ end
+
+ def conflicts?
+ list_conflict_files.any?
end
def resolve_conflicts(target_repository, resolution, source_branch, target_branch)
@@ -58,38 +62,6 @@ module Gitlab
user: Gitlab::Git::User.from_gitlab(resolution.user).to_gitaly
)
end
-
- def files_from_response(response)
- files = []
-
- response.each do |msg|
- msg.files.each do |gitaly_file|
- if gitaly_file.header
- files << file_from_gitaly_header(gitaly_file.header)
- else
- files.last.content << gitaly_file.content
- end
- end
- end
-
- files
- end
-
- def file_from_gitaly_header(header)
- Gitlab::Git::Conflict::File.new(
- Gitlab::GitalyClient::Util.git_repository(header.repository),
- header.commit_oid,
- conflict_from_gitaly_file_header(header),
- ''
- )
- end
-
- def conflict_from_gitaly_file_header(header)
- {
- ours: { path: header.our_path, mode: header.our_mode },
- theirs: { path: header.their_path }
- }
- end
end
end
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 37ba1af8d6f..7319de69d13 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -147,6 +147,34 @@ module Gitlab
start_repository: start_repository)
end
+ def user_rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
+ request = Gitaly::UserRebaseRequest.new(
+ repository: @gitaly_repo,
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ rebase_id: rebase_id.to_s,
+ branch: encode_binary(branch),
+ branch_sha: branch_sha,
+ remote_repository: remote_repository.gitaly_repository,
+ remote_branch: encode_binary(remote_branch)
+ )
+
+ response = GitalyClient.call(
+ @repository.storage,
+ :operation_service,
+ :user_rebase,
+ request,
+ remote_storage: remote_repository.storage
+ )
+
+ if response.pre_receive_error.presence
+ raise Gitlab::Git::HooksService::PreReceiveError, response.pre_receive_error
+ elsif response.git_error.presence
+ raise Gitlab::Git::Repository::GitError, response.git_error
+ else
+ response.rebase_sha
+ end
+ end
+
private
def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 66006f5dc5b..12016aee2a6 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -100,6 +100,38 @@ module Gitlab
)
end
+ def import_repository(source)
+ request = Gitaly::CreateRepositoryFromURLRequest.new(
+ repository: @gitaly_repo,
+ url: source
+ )
+
+ GitalyClient.call(
+ @storage,
+ :repository_service,
+ :create_repository_from_url,
+ request,
+ timeout: GitalyClient.default_timeout
+ )
+ end
+
+ def rebase_in_progress?(rebase_id)
+ request = Gitaly::IsRebaseInProgressRequest.new(
+ repository: @gitaly_repo,
+ rebase_id: rebase_id.to_s
+ )
+
+ response = GitalyClient.call(
+ @storage,
+ :repository_service,
+ :is_rebase_in_progress,
+ request,
+ timeout: GitalyClient.default_timeout
+ )
+
+ response.in_progress
+ end
+
def fetch_source_branch(source_repository, source_branch, local_ref)
request = Gitaly::FetchSourceBranchRequest.new(
repository: @gitaly_repo,
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 5da9befa08e..4f160e4a447 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -14,6 +14,8 @@ module Gitlab
# puts label.name
# end
class Client
+ include ::Gitlab::Utils::StrongMemoize
+
attr_reader :octokit
# A single page of data and the corresponding page number.
@@ -173,7 +175,9 @@ module Gitlab
end
def rate_limiting_enabled?
- @rate_limiting_enabled ||= api_endpoint.include?('.github.com')
+ strong_memoize(:rate_limiting_enabled) do
+ api_endpoint.include?('.github.com')
+ end
end
def api_endpoint
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
index 0f4ba6f83fc..672b5579dfd 100644
--- a/lib/gitlab/gpg/commit.rb
+++ b/lib/gitlab/gpg/commit.rb
@@ -4,12 +4,8 @@ module Gitlab
def initialize(commit)
@commit = commit
- @signature_text, @signed_text =
- begin
- Rugged::Commit.extract_signature(@commit.project.repository.rugged, @commit.sha)
- rescue Rugged::OdbError
- nil
- end
+ repo = commit.project.repository.raw_repository
+ @signature_text, @signed_text = Gitlab::Git::Commit.extract_signature(repo, commit.sha)
end
def has_signature?
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 989342389bc..5c971564a73 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -17,12 +17,16 @@ module Gitlab
def import
mkdir_p(@shared.export_path)
+ remove_symlinks!
+
wait_for_archived_file do
decompress_archive
end
rescue => e
@shared.error(e)
false
+ ensure
+ remove_symlinks!
end
private
@@ -43,7 +47,7 @@ module Gitlab
raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
- remove_symlinks!
+ result
end
def remove_symlinks!
diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb
index 6130c124dd1..2daeba90a51 100644
--- a/lib/gitlab/import_export/saver.rb
+++ b/lib/gitlab/import_export/saver.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def archive_file
- @archive_file ||= File.join(@shared.export_path, '..', Gitlab::ImportExport.export_filename(project: @project))
+ @archive_file ||= File.join(@shared.archive_path, Gitlab::ImportExport.export_filename(project: @project))
end
end
end
diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index 9fd0b709ef2..d03cbc880fd 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -9,7 +9,11 @@ module Gitlab
end
def export_path
- @export_path ||= Gitlab::ImportExport.export_path(relative_path: opts[:relative_path])
+ @export_path ||= Gitlab::ImportExport.export_path(relative_path: relative_path)
+ end
+
+ def archive_path
+ @archive_path ||= Gitlab::ImportExport.export_path(relative_path: relative_archive_path)
end
def error(error)
@@ -21,6 +25,14 @@ module Gitlab
private
+ def relative_path
+ File.join(opts[:relative_path], SecureRandom.hex)
+ end
+
+ def relative_archive_path
+ File.join(opts[:relative_path], '..')
+ end
+
def error_out(message, caller)
Rails.logger.error("Import/Export error raised on #{caller}: #{message}")
end
diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb
index 8d8c441a4b1..bf6981035f4 100644
--- a/lib/gitlab/kubernetes/helm/install_command.rb
+++ b/lib/gitlab/kubernetes/helm/install_command.rb
@@ -36,7 +36,11 @@ module Gitlab
def complete_command(namespace_name)
return unless chart
- "helm install #{chart} --name #{name} --namespace #{namespace_name} >/dev/null"
+ if chart_values_file
+ "helm install #{chart} --name #{name} --namespace #{namespace_name} -f /data/helm/#{name}/config/values.yaml >/dev/null"
+ else
+ "helm install #{chart} --name #{name} --namespace #{namespace_name} >/dev/null"
+ end
end
def install_dps_command
diff --git a/lib/gitlab/kubernetes/helm/pod.rb b/lib/gitlab/kubernetes/helm/pod.rb
index 97ad3c97e95..a3216759cae 100644
--- a/lib/gitlab/kubernetes/helm/pod.rb
+++ b/lib/gitlab/kubernetes/helm/pod.rb
@@ -10,9 +10,10 @@ module Gitlab
def generate
spec = { containers: [container_specification], restartPolicy: 'Never' }
+
if command.chart_values_file
- generate_config_map
- spec['volumes'] = volumes_specification
+ create_config_map
+ spec[:volumes] = volumes_specification
end
::Kubeclient::Resource.new(metadata: metadata, spec: spec)
@@ -35,19 +36,39 @@ module Gitlab
end
def labels
- { 'gitlab.org/action': 'install', 'gitlab.org/application': command.name }
+ {
+ 'gitlab.org/action': 'install',
+ 'gitlab.org/application': command.name
+ }
end
def metadata
- { name: command.pod_name, namespace: namespace_name, labels: labels }
+ {
+ name: command.pod_name,
+ namespace: namespace_name,
+ labels: labels
+ }
end
def volume_mounts_specification
- [{ name: 'config-volume', mountPath: '/etc/config' }]
+ [
+ {
+ name: 'configuration-volume',
+ mountPath: "/data/helm/#{command.name}/config"
+ }
+ ]
end
def volumes_specification
- [{ name: 'config-volume', configMap: { name: 'values-config' } }]
+ [
+ {
+ name: 'configuration-volume',
+ configMap: {
+ name: 'values-content-configuration',
+ items: [{ key: 'values', path: 'values.yaml' }]
+ }
+ }
+ ]
end
def generate_pod_env(command)
@@ -58,10 +79,10 @@ module Gitlab
}.map { |key, value| { name: key, value: value } }
end
- def generate_config_map
+ def create_config_map
resource = ::Kubeclient::Resource.new
- resource.metadata = { name: 'values-config', namespace: namespace_name }
- resource.data = YAML.load_file(command.chart_values_file)
+ resource.metadata = { name: 'values-content-configuration', namespace: namespace_name, labels: { name: 'values-content-configuration' } }
+ resource.data = { values: File.read(command.chart_values_file) }
kubeclient.create_config_map(resource)
end
end
diff --git a/lib/gitlab/o_auth.rb b/lib/gitlab/o_auth.rb
new file mode 100644
index 00000000000..5ad8d83bd6e
--- /dev/null
+++ b/lib/gitlab/o_auth.rb
@@ -0,0 +1,6 @@
+module Gitlab
+ module OAuth
+ SignupDisabledError = Class.new(StandardError)
+ SigninDisabledForProviderError = Class.new(StandardError)
+ end
+end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index d33f33d192f..fff9360ea27 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -5,8 +5,6 @@
#
module Gitlab
module OAuth
- SignupDisabledError = Class.new(StandardError)
-
class User
attr_accessor :auth_hash, :gl_user
@@ -29,7 +27,8 @@ module Gitlab
end
def save(provider = 'OAuth')
- unauthorized_to_create unless gl_user
+ raise SigninDisabledForProviderError if oauth_provider_disabled?
+ raise SignupDisabledError unless gl_user
block_after_save = needs_blocking?
@@ -226,8 +225,10 @@ module Gitlab
Gitlab::AppLogger
end
- def unauthorized_to_create
- raise SignupDisabledError
+ def oauth_provider_disabled?
+ Gitlab::CurrentSettings.current_application_settings
+ .disabled_oauth_sign_in_sources
+ .include?(auth_hash.provider)
end
end
end
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index e73245b82c1..e29e168fc5a 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -6,6 +6,7 @@ module Gitlab
EXPIRY_TIME = 5.minutes
def self.enabled?(user = nil)
+ return true if Rails.env.development?
return false unless user && allowed_group_id
allowed_user_ids.include?(user.id)
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
new file mode 100644
index 00000000000..95d94b3cc68
--- /dev/null
+++ b/lib/gitlab/profiler.rb
@@ -0,0 +1,142 @@
+# coding: utf-8
+module Gitlab
+ module Profiler
+ FILTERED_STRING = '[FILTERED]'.freeze
+
+ IGNORE_BACKTRACES = %w[
+ lib/gitlab/i18n.rb
+ lib/gitlab/request_context.rb
+ config/initializers
+ lib/gitlab/database/load_balancing/
+ lib/gitlab/etag_caching/
+ lib/gitlab/metrics/
+ lib/gitlab/middleware/
+ lib/gitlab/performance_bar/
+ lib/gitlab/request_profiler/
+ lib/gitlab/profiler.rb
+ ].freeze
+
+ # Takes a URL to profile (can be a fully-qualified URL, or an absolute path)
+ # and returns the ruby-prof profile result. Formatting that result is the
+ # caller's responsibility. Requests are GET requests unless post_data is
+ # passed.
+ #
+ # Optional arguments:
+ # - logger: will be used for SQL logging, including a summary at the end of
+ # the log file of the total time spent per model class.
+ #
+ # - post_data: a string of raw POST data to use. Changes the HTTP verb to
+ # POST.
+ #
+ # - user: a user to authenticate as. Only works if the user has a valid
+ # personal access token.
+ #
+ # - private_token: instead of providing a user instance, the token can be
+ # given as a string. Takes precedence over the user option.
+ def self.profile(url, logger: nil, post_data: nil, user: nil, private_token: nil)
+ app = ActionDispatch::Integration::Session.new(Rails.application)
+ verb = :get
+ headers = {}
+
+ if post_data
+ verb = :post
+ headers['Content-Type'] = 'application/json'
+ end
+
+ if user
+ private_token ||= user.personal_access_tokens.active.pluck(:token).first
+ end
+
+ headers['Private-Token'] = private_token if private_token
+ logger = create_custom_logger(logger, private_token: private_token)
+
+ RequestStore.begin!
+
+ # Make an initial call for an asset path in development mode to avoid
+ # sprockets dominating the profiler output.
+ ActionController::Base.helpers.asset_path('katex.css') if Rails.env.development?
+
+ # Rails loads internationalization files lazily the first time a
+ # translation is needed. Running this prevents this overhead from showing
+ # up in profiles.
+ ::I18n.t('.')[:test_string]
+
+ # Remove API route mounting from the profile.
+ app.get('/api/v4/users')
+
+ result = with_custom_logger(logger) do
+ RubyProf.profile { app.public_send(verb, url, post_data, headers) } # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ RequestStore.end!
+
+ log_load_times_by_model(logger)
+
+ result
+ end
+
+ def self.create_custom_logger(logger, private_token: nil)
+ return unless logger
+
+ logger.dup.tap do |new_logger|
+ new_logger.instance_variable_set(:@private_token, private_token)
+
+ class << new_logger
+ attr_reader :load_times_by_model, :private_token
+
+ def debug(message, *)
+ message.gsub!(private_token, FILTERED_STRING) if private_token
+
+ _, type, time = *message.match(/(\w+) Load \(([0-9.]+)ms\)/)
+
+ if type && time
+ @load_times_by_model ||= {}
+ @load_times_by_model[type] ||= 0
+ @load_times_by_model[type] += time.to_f
+ end
+
+ super
+
+ backtrace = Rails.backtrace_cleaner.clean(caller)
+
+ backtrace.each do |caller_line|
+ next if caller_line.match(Regexp.union(IGNORE_BACKTRACES))
+
+ stripped_caller_line = caller_line.sub("#{Rails.root}/", '')
+
+ super(" ↳ #{stripped_caller_line}")
+ end
+ end
+ end
+ end
+ end
+
+ def self.with_custom_logger(logger)
+ original_colorize_logging = ActiveSupport::LogSubscriber.colorize_logging
+ original_activerecord_logger = ActiveRecord::Base.logger
+ original_actioncontroller_logger = ActionController::Base.logger
+
+ if logger
+ ActiveSupport::LogSubscriber.colorize_logging = false
+ ActiveRecord::Base.logger = logger
+ ActionController::Base.logger = logger
+ end
+
+ result = yield
+
+ ActiveSupport::LogSubscriber.colorize_logging = original_colorize_logging
+ ActiveRecord::Base.logger = original_activerecord_logger
+ ActionController::Base.logger = original_actioncontroller_logger
+
+ result
+ end
+
+ def self.log_load_times_by_model(logger)
+ return unless logger.respond_to?(:load_times_by_model)
+
+ logger.load_times_by_model.to_a.sort_by(&:last).reverse.each do |(model, time)|
+ logger.info("#{model} total: #{time.round(2)}ms")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index e2662fc362b..7771b15069b 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -44,25 +44,20 @@ module Gitlab
ref = nil
filename = nil
basename = nil
+ data = ""
startline = 0
- result.each_line.each_with_index do |line, index|
- matches = line.match(/^(?<ref>[^:]*):(?<filename>.*):(?<startline>\d+):/)
- if matches
+ result.strip.each_line.each_with_index do |line, index|
+ prefix ||= line.match(/^(?<ref>[^:]*):(?<filename>.*)\x00(?<startline>\d+)\x00/)&.tap do |matches|
ref = matches[:ref]
filename = matches[:filename]
startline = matches[:startline]
startline = startline.to_i - index
extname = Regexp.escape(File.extname(filename))
basename = filename.sub(/#{extname}$/, '')
- break
end
- end
-
- data = ""
- result.each_line do |line|
- data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
+ data << line.sub(prefix.to_s, '')
end
FoundBlob.new(
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 0002c7da8f1..7ab85e1c35c 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -67,7 +67,7 @@ module Gitlab
end
def build_trace_section_regex
- @build_trace_section_regexp ||= /section_((?:start)|(?:end)):(\d+):([^\r]+)\r\033\[0K/.freeze
+ @build_trace_section_regexp ||= /section_((?:start)|(?:end)):(\d+):([a-zA-Z0-9_.-]+)\r\033\[0K/.freeze
end
end
end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index d9a5af09f08..f357488ac61 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -16,8 +16,10 @@ module Gitlab
def can_do_action?(action)
return false unless can_access_git?
- @permission_cache ||= {}
- @permission_cache[action] ||= user.can?(action, project)
+ permission_cache[action] =
+ permission_cache.fetch(action) do
+ user.can?(action, project)
+ end
end
def cannot_do_action?(action)
@@ -88,6 +90,10 @@ module Gitlab
private
+ def permission_cache
+ @permission_cache ||= {}
+ end
+
def can_access_git?
user && user.can?(:access_git)
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index b3baaf036d8..fa22f0e37b2 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -27,6 +27,10 @@ module Gitlab
.gsub(/(\A-+|-+\z)/, '')
end
+ def remove_line_breaks(str)
+ str.gsub(/\r?\n/, '')
+ end
+
def to_boolean(value)
return value if [true, false].include?(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i
diff --git a/lib/gitlab/utils/override.rb b/lib/gitlab/utils/override.rb
new file mode 100644
index 00000000000..8bf6bcb1fe2
--- /dev/null
+++ b/lib/gitlab/utils/override.rb
@@ -0,0 +1,111 @@
+module Gitlab
+ module Utils
+ module Override
+ class Extension
+ def self.verify_class!(klass, method_name)
+ instance_method_defined?(klass, method_name) ||
+ raise(
+ NotImplementedError.new(
+ "#{klass}\##{method_name} doesn't exist!"))
+ end
+
+ def self.instance_method_defined?(klass, name, include_super: true)
+ klass.instance_methods(include_super).include?(name) ||
+ klass.private_instance_methods(include_super).include?(name)
+ end
+
+ attr_reader :subject
+
+ def initialize(subject)
+ @subject = subject
+ end
+
+ def add_method_name(method_name)
+ method_names << method_name
+ end
+
+ def add_class(klass)
+ classes << klass
+ end
+
+ def verify!
+ classes.each do |klass|
+ index = klass.ancestors.index(subject)
+ parents = klass.ancestors.drop(index + 1)
+
+ method_names.each do |method_name|
+ parents.any? do |parent|
+ self.class.instance_method_defined?(
+ parent, method_name, include_super: false)
+ end ||
+ raise(
+ NotImplementedError.new(
+ "#{klass}\##{method_name} doesn't exist!"))
+ end
+ end
+ end
+
+ private
+
+ def method_names
+ @method_names ||= []
+ end
+
+ def classes
+ @classes ||= []
+ end
+ end
+
+ # Instead of writing patterns like this:
+ #
+ # def f
+ # raise NotImplementedError unless defined?(super)
+ #
+ # true
+ # end
+ #
+ # We could write it like:
+ #
+ # extend ::Gitlab::Utils::Override
+ #
+ # override :f
+ # def f
+ # true
+ # end
+ #
+ # This would make sure we're overriding something. See:
+ # https://gitlab.com/gitlab-org/gitlab-ee/issues/1819
+ def override(method_name)
+ return unless ENV['STATIC_VERIFICATION']
+
+ if is_a?(Class)
+ Extension.verify_class!(self, method_name)
+ else # We delay the check for modules
+ Override.extensions[self] ||= Extension.new(self)
+ Override.extensions[self].add_method_name(method_name)
+ end
+ end
+
+ def included(base = nil)
+ return super if base.nil? # Rails concern, ignoring it
+
+ super
+
+ if base.is_a?(Class) # We could check for Class in `override`
+ # This could be `nil` if `override` was never called
+ Override.extensions[self]&.add_class(base)
+ end
+ end
+
+ alias_method :prepended, :included
+
+ def self.extensions
+ @extensions ||= {}
+ end
+
+ def self.verify!
+ extensions.values.each(&:verify!)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 0de183858aa..633da44b22d 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -34,7 +34,10 @@ module Gitlab
feature_enabled = case action.to_s
when 'git_receive_pack'
- Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
+ Gitlab::GitalyClient.feature_enabled?(
+ :post_receive_pack,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT
+ )
when 'git_upload_pack'
true
when 'info_refs'
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index e65609d7001..4beb94eeb8e 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -7,4 +7,9 @@ namespace :dev do
Rake::Task["gitlab:setup"].invoke
Rake::Task["gitlab:shell:setup"].invoke
end
+
+ desc "GitLab | Eager load application"
+ task load: :environment do
+ Rails.application.eager_load!
+ end
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 31cd6bfe6e1..a584eb97cf5 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -427,7 +427,7 @@ namespace :gitlab do
namespace :user do
desc "GitLab | Check the integrity of a specific user's repositories"
task :check_repos, [:username] => :environment do |t, args|
- username = args[:username] || prompt("Check repository integrity for fsername? ".color(:blue))
+ username = args[:username] || prompt("Check repository integrity for username? ".color(:blue))
user = User.find_by(username: username)
if user
repo_dirs = user.authorized_projects.map do |p|
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 0e6aed32c52..12ae4199b69 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -54,16 +54,6 @@ namespace :gitlab do
# (Re)create hooks
Rake::Task['gitlab:shell:create_hooks'].invoke
- # Required for debian packaging with PKGR: Setup .ssh/environment with
- # the current PATH, so that the correct ruby version gets loaded
- # Requires to set "PermitUserEnvironment yes" in sshd config (should not
- # be an issue since it is more than likely that there are no "normal"
- # user accounts on a gitlab server). The alternative is for the admin to
- # install a ruby (1.9.3+) in the global path.
- File.open(File.join(user_home, ".ssh", "environment"), "w+") do |f|
- f.puts "PATH=#{ENV['PATH']}"
- end
-
Gitlab::Shell.ensure_secret_token!
end
diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake
index 7b63e93db0e..3ab406eff2c 100644
--- a/lib/tasks/lint.rake
+++ b/lib/tasks/lint.rake
@@ -1,5 +1,17 @@
unless Rails.env.production?
namespace :lint do
+ task :static_verification_env do
+ ENV['STATIC_VERIFICATION'] = 'true'
+ end
+
+ desc "GitLab | lint | Static verification"
+ task static_verification: %w[
+ lint:static_verification_env
+ dev:load
+ ] do
+ Gitlab::Utils::Override.verify!
+ end
+
desc "GitLab | lint | Lint JavaScript files using ESLint"
task :javascript do
Rake::Task['eslint'].invoke
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
index c9e3eed82f2..c996537cfbe 100644
--- a/lib/tasks/migrate/setup_postgresql.rake
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -7,6 +7,7 @@ require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like')
require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes')
require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like')
require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb')
+require Rails.root.join('db/migrate/20180113220114_rework_redirect_routes_indexes.rb')
desc 'GitLab | Sets up PostgreSQL'
task setup_postgresql: :environment do
@@ -17,4 +18,5 @@ task setup_postgresql: :environment do
AddLowerPathIndexToRedirectRoutes.new.up
IndexRedirectRoutesPathForLike.new.up
AddIndexOnNamespacesLowerName.new.up
+ ReworkRedirectRoutesIndexes.new.up
end
diff --git a/package.json b/package.json
index 4759ae76817..b3dedf126c5 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
"js-cookie": "^2.1.3",
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
- "marked": "^0.3.6",
+ "marked": "^0.3.12",
"monaco-editor": "0.10.0",
"mousetrap": "^1.4.6",
"name-all-modules-plugin": "^1.0.1",
@@ -64,6 +64,7 @@
"raven-js": "^3.14.0",
"raw-loader": "^0.5.1",
"react-dev-utils": "^0.5.2",
+ "sanitize-html": "^1.16.1",
"select2": "3.5.2-browserify",
"sql.js": "^0.4.0",
"svg4everybody": "2.1.9",
@@ -101,13 +102,13 @@
"istanbul": "^0.4.5",
"jasmine-core": "^2.6.3",
"jasmine-jquery": "^2.1.1",
- "karma": "^1.7.0",
- "karma-chrome-launcher": "^2.1.1",
- "karma-coverage-istanbul-reporter": "^0.2.0",
- "karma-jasmine": "^1.1.0",
- "karma-mocha-reporter": "^2.2.2",
+ "karma": "^2.0.0",
+ "karma-chrome-launcher": "^2.2.0",
+ "karma-coverage-istanbul-reporter": "^1.3.3",
+ "karma-jasmine": "^1.1.1",
+ "karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^2.0.4",
+ "karma-webpack": "2.0.7",
"nodemon": "^1.11.0",
"prettier": "1.9.2",
"webpack-dev-server": "^2.6.1"
diff --git a/qa/README.md b/qa/README.md
index 8fa04e80825..3c1b61900d9 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -17,6 +17,17 @@ against any existing instance.
1. Along with GitLab Docker Images we also build and publish GitLab QA images.
1. GitLab QA project uses these images to execute integration tests.
+## Validating GitLab views / partials / selectors in merge requests
+
+We recently added a new CI job that is going to be triggered for every push
+event in CE and EE projects. The job is called `qa:selectors` and it will
+verify coupling between page objects implemented as a part of GitLab QA
+and corresponding views / partials / selectors in CE / EE.
+
+Whenever `qa:selectors` job fails in your merge request, you are supposed to
+fix [page objects](qa/page/README.md). You should also trigger end-to-end tests
+using `package-qa` manual action, to test if everything works fine.
+
## How can I use it?
You can use GitLab QA to exercise tests on any live instance! For example, the
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 7b4c1603017..9cff2c5c317 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -19,15 +19,17 @@ module QA
end
def sign_in_using_credentials
- if page.has_content?('Change your password')
+ using_wait_time 0 do
+ if page.has_content?('Change your password')
+ fill_in :user_password, with: Runtime::User.password
+ fill_in :user_password_confirmation, with: Runtime::User.password
+ click_button 'Change your password'
+ end
+
+ fill_in :user_login, with: Runtime::User.name
fill_in :user_password, with: Runtime::User.password
- fill_in :user_password_confirmation, with: Runtime::User.password
- click_button 'Change your password'
+ click_button 'Sign in'
end
-
- fill_in :user_login, with: Runtime::User.name
- fill_in :user_password, with: Runtime::User.password
- click_button 'Sign in'
end
def self.path
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
index 5d1d5120929..b4ef07e1540 100644
--- a/qa/qa/page/project/settings/common.rb
+++ b/qa/qa/page/project/settings/common.rb
@@ -3,9 +3,9 @@ module QA
module Project
module Settings
module Common
- def expand(selector)
+ def expand(element_name)
page.within('#content-body') do
- find(selector).click
+ click_element(element_name)
yield
end
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index a8d6f09777c..bf42767c707 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -3,12 +3,19 @@ module QA
module Project
module Settings
class DeployKeys < Page::Base
- ##
- # TODO, define all selectors required by this page object
- #
- # See gitlab-org/gitlab-qa#154
- #
- view 'app/views/projects/deploy_keys/edit.html.haml'
+ view 'app/views/projects/deploy_keys/_form.html.haml' do
+ element :deploy_key_title, 'text_field :title'
+ element :deploy_key_key, 'text_area :key'
+ end
+
+ view 'app/assets/javascripts/deploy_keys/components/app.vue' do
+ element :deploy_keys_section, /class=".*deploy\-keys.*"/
+ end
+
+ view 'app/assets/javascripts/deploy_keys/components/key.vue' do
+ element :key_title, /class=".*title.*"/
+ element :key_title_field, '{{ deployKey.title }}'
+ end
def fill_key_title(title)
fill_in 'deploy_key_title', with: title
diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb
index 524d87c6be9..6cc68358c8c 100644
--- a/qa/qa/page/project/settings/repository.rb
+++ b/qa/qa/page/project/settings/repository.rb
@@ -5,15 +5,12 @@ module QA
class Repository < Page::Base
include Common
- ##
- # TODO, define all selectors required by this page object
- #
- # See gitlab-org/gitlab-qa#154
- #
- view 'app/views/projects/settings/repository/show.html.haml'
+ view 'app/views/projects/deploy_keys/_index.html.haml' do
+ element :expand_deploy_keys
+ end
def expand_deploy_keys(&block)
- expand('.qa-expand-deploy-keys') do
+ expand(:expand_deploy_keys) do
DeployKeys.perform(&block)
end
end
diff --git a/rubocop/cop/gitlab/predicate_memoization.rb b/rubocop/cop/gitlab/predicate_memoization.rb
new file mode 100644
index 00000000000..3c25d61d087
--- /dev/null
+++ b/rubocop/cop/gitlab/predicate_memoization.rb
@@ -0,0 +1,39 @@
+module RuboCop
+ module Cop
+ module Gitlab
+ class PredicateMemoization < RuboCop::Cop::Cop
+ MSG = <<~EOL.freeze
+ Avoid using `@value ||= query` inside predicate methods in order to
+ properly memoize `false` or `nil` values.
+ https://docs.gitlab.com/ee/development/utilities.html#strongmemoize
+ EOL
+
+ def on_def(node)
+ return unless predicate_method?(node)
+
+ select_offenses(node).each do |offense|
+ add_offense(offense, location: :expression)
+ end
+ end
+
+ private
+
+ def predicate_method?(node)
+ node.method_name.to_s.end_with?('?')
+ end
+
+ def or_ivar_assignment?(or_assignment)
+ lhs = or_assignment.each_child_node.first
+
+ lhs.ivasgn_type?
+ end
+
+ def select_offenses(node)
+ node.each_descendant(:or_asgn).select do |or_assignment|
+ or_ivar_assignment?(or_assignment)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 57af87f7fb9..9110237c538 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,4 +1,5 @@
require_relative 'cop/gitlab/module_with_instance_variables'
+require_relative 'cop/gitlab/predicate_memoization'
require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/line_break_around_conditional_block'
require_relative 'cop/migration/add_column'
diff --git a/scripts/add-code-formatters b/scripts/add-code-formatters
deleted file mode 100755
index 56bb8754d80..00000000000
--- a/scripts/add-code-formatters
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/sh
-
-# Check if file exists with -f. Check if in in the gdk rook directory.
-if [ ! -f ../GDK_ROOT ]; then
- echo "Please run script from gitlab (e.g. gitlab-development-kit/gitlab) root directory."
- exit 1
-fi
-
-PRECOMMIT=$(git rev-parse --git-dir)/hooks/pre-commit
-
-# Check if symlink exists with -L. Check if script was already installed.
-if [ -L $PRECOMMIT ]; then
- echo "Pre-commit script already installed."
- exit 1
-fi
-
-ln -s ./pre-commit $PRECOMMIT
-echo "Pre-commit script installed successfully"
diff --git a/scripts/pre-commit b/scripts/pre-commit
deleted file mode 100644
index 48935e90a87..00000000000
--- a/scripts/pre-commit
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/sh
-
-# Check if file exists with -f. Check if in in the gdk rook directory.
-if [ ! -f ../GDK_ROOT ]; then
- echo "Please run pre-commit from gitlab (e.g. gitlab-development-kit/gitlab) root directory."
- exit 1
-fi
-
-jsfiles=$(git diff --cached --name-only --diff-filter=ACM "*.js" | tr '\n' ' ')
-[ -z "$jsfiles" ] && exit 0
-
-# Prettify all staged .js files
-echo "$jsfiles" | xargs ./node_modules/.bin/prettier --write
-
-# Add back the modified/prettified files to staging
-echo "$jsfiles" | xargs git add
-
-exit 0
diff --git a/scripts/static-analysis b/scripts/static-analysis
index 2a2bc67800d..9690b42c788 100755
--- a/scripts/static-analysis
+++ b/scripts/static-analysis
@@ -10,9 +10,10 @@ tasks = [
%w[bundle exec license_finder],
%w[yarn run eslint],
%w[bundle exec rubocop --parallel],
- %w[scripts/lint-conflicts.sh],
%w[bundle exec rake gettext:lint],
- %w[scripts/lint-changelog-yaml]
+ %w[bundle exec rake lint:static_verification],
+ %w[scripts/lint-changelog-yaml],
+ %w[scripts/lint-conflicts.sh]
]
failed_tasks = tasks.reduce({}) do |failures, task|
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 2565622f8df..cc1b1e5039e 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -51,6 +51,13 @@ describe Admin::ApplicationSettingsController do
sign_in(admin)
end
+ it 'updates the password_authentication_enabled_for_git setting' do
+ put :update, application_setting: { password_authentication_enabled_for_git: "0" }
+
+ expect(response).to redirect_to(admin_application_settings_path)
+ expect(ApplicationSetting.current.password_authentication_enabled_for_git).to eq(false)
+ end
+
it 'updates the default_project_visibility for string value' do
put :update, application_setting: { default_project_visibility: "20" }
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb
index e6ba596117a..d2c1e634930 100644
--- a/spec/controllers/admin/hooks_controller_spec.rb
+++ b/spec/controllers/admin/hooks_controller_spec.rb
@@ -11,11 +11,13 @@ describe Admin::HooksController do
it 'sets all parameters' do
hook_params = {
enable_ssl_verification: true,
+ token: "TEST TOKEN",
+ url: "http://example.com",
+
push_events: true,
tag_push_events: true,
repository_update_events: true,
- token: "TEST TOKEN",
- url: "http://example.com"
+ merge_requests_events: true
}
post :create, hook: hook_params
diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb
index fb9d3efbac0..7f2eaf95165 100644
--- a/spec/controllers/dashboard/groups_controller_spec.rb
+++ b/spec/controllers/dashboard/groups_controller_spec.rb
@@ -20,4 +20,24 @@ describe Dashboard::GroupsController do
expect(assigns(:groups)).to contain_exactly(member_of_group)
end
+
+ context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do
+ let!(:top_level_result) { create(:group, name: 'chef-top') }
+ let!(:top_level_a) { create(:group, name: 'top-a') }
+ let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) }
+ let!(:other_group) { create(:group, name: 'other') }
+
+ before do
+ top_level_result.add_master(user)
+ top_level_a.add_master(user)
+ end
+
+ it 'renders only groups the user is a member of when searching hierarchy correctly' do
+ get :index, filter: 'chef', format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ all_groups = [top_level_result, top_level_a, sub_level_result_a]
+ expect(assigns(:groups)).to contain_exactly(*all_groups)
+ end
+ end
end
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index cb1b460fc0e..22d3076c269 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -160,6 +160,30 @@ describe Groups::ChildrenController do
expect(json_response).to eq([])
end
+ it 'succeeds if multiple pages contain matching subgroups' do
+ create(:group, parent: group, name: 'subgroup-filter-1')
+ create(:group, parent: group, name: 'subgroup-filter-2')
+
+ # Creating the group-to-nest first so it would be loaded into the
+ # relation first before it's parents, this is what would cause the
+ # crash in: https://gitlab.com/gitlab-org/gitlab-ce/issues/40785.
+ #
+ # If we create the parent groups first, those would be loaded into the
+ # collection first, and the pagination would cut off the actual search
+ # result. In this case the hierarchy can be rendered without crashing,
+ # it's just incomplete.
+ group_to_nest = create(:group, parent: group, name: 'subsubgroup-filter-3')
+ subgroup = create(:group, parent: group)
+ 3.times do |i|
+ subgroup = create(:group, parent: subgroup)
+ end
+ group_to_nest.update!(parent: subgroup)
+
+ get :index, group_id: group.to_param, filter: 'filter', per_page: 3, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it 'includes pagination headers' do
2.times { |i| create(:group, :public, parent: public_subgroup, name: "filterme#{i}") }
diff --git a/spec/controllers/import/gitlab_projects_controller_spec.rb b/spec/controllers/import/gitlab_projects_controller_spec.rb
new file mode 100644
index 00000000000..8759d3c0b97
--- /dev/null
+++ b/spec/controllers/import/gitlab_projects_controller_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Import::GitlabProjectsController do
+ set(:namespace) { create(:namespace) }
+ set(:user) { namespace.owner }
+ let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'POST create' do
+ context 'with an invalid path' do
+ it 'redirects with an error' do
+ post :create, namespace_id: namespace.id, path: '/test', file: file
+
+ expect(flash[:alert]).to start_with('Project could not be imported')
+ expect(response).to have_gitlab_http_status(302)
+ end
+
+ it 'redirects with an error when a relative path is used' do
+ post :create, namespace_id: namespace.id, path: '../test', file: file
+
+ expect(flash[:alert]).to start_with('Project could not be imported')
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'with a valid path' do
+ it 'redirects to the new project path' do
+ post :create, namespace_id: namespace.id, path: 'test', file: file
+
+ expect(flash[:notice]).to include('is being imported')
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
new file mode 100644
index 00000000000..c639ad32ec6
--- /dev/null
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe OmniauthCallbacksController do
+ include LoginHelpers
+
+ let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: provider) }
+ let(:provider) { :github }
+
+ before do
+ mock_auth_hash(provider.to_s, 'my-uid', user.email)
+ stub_omniauth_provider(provider, context: request)
+ end
+
+ it 'allows sign in' do
+ post provider
+
+ expect(request.env['warden']).to be_authenticated
+ end
+
+ shared_context 'sign_up' do
+ let(:user) { double(email: 'new@example.com') }
+
+ before do
+ stub_omniauth_setting(block_auto_created_users: false)
+ end
+ end
+
+ context 'sign up' do
+ include_context 'sign_up'
+
+ it 'is allowed' do
+ post provider
+
+ expect(request.env['warden']).to be_authenticated
+ end
+ end
+
+ context 'when OAuth is disabled' do
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ settings = Gitlab::CurrentSettings.current_application_settings
+ settings.update(disabled_oauth_sign_in_sources: [provider.to_s])
+ end
+
+ it 'prevents login via POST' do
+ post provider
+
+ expect(request.env['warden']).not_to be_authenticated
+ end
+
+ it 'shows warning when attempting login' do
+ post provider
+
+ expect(response).to redirect_to new_user_session_path
+ expect(flash[:alert]).to eq('Signing in using GitHub has been disabled')
+ end
+
+ it 'allows linking the disabled provider' do
+ user.identities.destroy_all
+ sign_in(user)
+
+ expect { post provider }.to change { user.reload.identities.count }.by(1)
+ end
+
+ context 'sign up' do
+ include_context 'sign_up'
+
+ it 'is prevented' do
+ post provider
+
+ expect(request.env['warden']).not_to be_authenticated
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index 3bbe168f6d5..6a41c4d23ea 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Projects::AvatarsController do
- let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let(:project) { create(:project, :repository, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:user) { create(:user) }
before do
@@ -10,6 +10,12 @@ describe Projects::AvatarsController do
controller.instance_variable_set(:@project, project)
end
+ it 'GET #show' do
+ get :show, namespace_id: project.namespace.id, project_id: project.id
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
it 'removes avatar from DB by calling destroy' do
delete :destroy, namespace_id: project.namespace.id, project_id: project.id
expect(project.avatar.present?).to be_falsey
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 73fb90d73ec..55ed276f96b 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -41,15 +41,21 @@ describe Projects::CommitsController do
context "when the ref name ends in .atom" do
context "when the ref does not exist with the suffix" do
- it "renders as atom" do
+ before do
get(:show,
namespace_id: project.namespace,
project_id: project,
id: "master.atom")
+ end
+ it "renders as atom" do
expect(response).to be_success
expect(response.content_type).to eq('application/atom+xml')
end
+
+ it 'renders summary with type=html' do
+ expect(response.body).to include('<summary type="html">')
+ end
end
context "when the ref exists with the suffix" do
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
index aba70c6d4c1..2d473d5bf52 100644
--- a/spec/controllers/projects/hooks_controller_spec.rb
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -18,4 +18,30 @@ describe Projects::HooksController do
)
end
end
+
+ describe '#create' do
+ it 'sets all parameters' do
+ hook_params = {
+ enable_ssl_verification: true,
+ token: "TEST TOKEN",
+ url: "http://example.com",
+
+ push_events: true,
+ tag_push_events: true,
+ merge_requests_events: true,
+ issues_events: true,
+ confidential_issues_events: true,
+ note_events: true,
+ job_events: true,
+ pipeline_events: true,
+ wiki_page_events: true
+ }
+
+ post :create, namespace_id: project.namespace, project_id: project, hook: hook_params
+
+ expect(response).to have_http_status(302)
+ expect(ProjectHook.all.size).to eq(1)
+ expect(ProjectHook.first).to have_attributes(hook_params)
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 6b7db947216..4a2998b4ccd 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -301,6 +301,53 @@ describe Projects::IssuesController do
end
end
+ describe 'GET #realtime_changes' do
+ def go(id:)
+ get :realtime_changes,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ end
+
+ context 'when an issue was edited' do
+ before do
+ project.add_developer(user)
+
+ issue.update!(last_edited_by: user, last_edited_at: issue.created_at + 1.minute)
+
+ sign_in(user)
+ end
+
+ it 'returns last edited time' do
+ go(id: issue.iid)
+
+ data = JSON.parse(response.body)
+
+ expect(data).to include('updated_at')
+ expect(data['updated_at']).to eq(issue.last_edited_at.to_time.iso8601)
+ end
+ end
+
+ context 'when an issue was edited by a deleted user' do
+ let(:deleted_user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+
+ issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now)
+
+ deleted_user.destroy
+ sign_in(user)
+ end
+
+ it 'returns 200' do
+ go(id: issue.iid)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+
describe 'Confidential Issues' do
let(:project) { create(:project_empty_repo, :public) }
let(:assignee) { create(:assignee) }
@@ -589,25 +636,6 @@ describe Projects::IssuesController do
project_id: project,
id: id
end
-
- context 'when an issue was edited by a deleted user' do
- let(:deleted_user) { create(:user) }
-
- before do
- project.add_developer(user)
-
- issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now)
-
- deleted_user.destroy
- sign_in(user)
- end
-
- it 'returns 200' do
- go(id: issue.iid)
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
end
describe 'GET #edit' do
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index 7e2366847f4..92db7284e0e 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -4,6 +4,16 @@ describe Projects::MergeRequests::CreationsController do
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:fork_project) { create(:forked_project_with_submodules) }
+ let(:get_diff_params) do
+ {
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project,
+ merge_request: {
+ source_branch: 'remove-submodule',
+ target_branch: 'master'
+ }
+ }
+ end
before do
fork_project.add_master(user)
@@ -13,18 +23,23 @@ describe Projects::MergeRequests::CreationsController do
describe 'GET new' do
context 'merge request that removes a submodule' do
- render_views
-
it 'renders new merge request widget template' do
- get :new,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project,
- merge_request: {
- source_branch: 'remove-submodule',
- target_branch: 'master'
- }
+ get :new, get_diff_params
+
+ expect(response).to be_success
+ end
+ end
+ end
+
+ describe 'GET diffs' do
+ context 'when merge request cannot be created' do
+ it 'does not assign diffs var' do
+ allow_any_instance_of(MergeRequest).to receive(:can_be_created).and_return(false)
+
+ get :diffs, get_diff_params.merge(format: 'json')
expect(response).to be_success
+ expect(assigns[:diffs]).to be_nil
end
end
end
@@ -37,14 +52,7 @@ describe Projects::MergeRequests::CreationsController do
end
it 'renders JSON including serialized pipelines' do
- get :pipelines,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project,
- merge_request: {
- source_branch: 'remove-submodule',
- target_branch: 'master'
- },
- format: :json
+ get :pipelines, get_diff_params.merge(format: 'json')
expect(response).to be_ok
expect(json_response).to have_key 'pipelines'
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
index 30a6d468ed3..4350652fb79 100644
--- a/spec/factories/deploy_keys_projects.rb
+++ b/spec/factories/deploy_keys_projects.rb
@@ -2,5 +2,9 @@ FactoryBot.define do
factory :deploy_keys_project do
deploy_key
project
+
+ trait :write_access do
+ can_push true
+ end
end
end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index 552b4b7e06e..f0c43f3d6f5 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -15,10 +15,6 @@ FactoryBot.define do
factory :another_deploy_key, class: 'DeployKey'
end
- factory :write_access_key, class: 'DeployKey' do
- can_push true
- end
-
factory :rsa_key_2048 do
key do
<<~KEY.delete("\n")
diff --git a/spec/factories/redirect_routes.rb b/spec/factories/redirect_routes.rb
new file mode 100644
index 00000000000..c29c81c5df9
--- /dev/null
+++ b/spec/factories/redirect_routes.rb
@@ -0,0 +1,15 @@
+FactoryBot.define do
+ factory :redirect_route do
+ sequence(:path) { |n| "redirect#{n}" }
+ source factory: :group
+ permanent false
+
+ trait :permanent do
+ permanent true
+ end
+
+ trait :temporary do
+ permanent false
+ end
+ end
+end
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index e020579f71e..51b42d1b43b 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -21,7 +21,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_selector('.row-content-block', text: 'All jobs')
expect(page.all('.build-link').size).to eq(4)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -31,7 +31,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -51,7 +51,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
expect(page.find('.build-link')).not_to have_content(build4.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -63,7 +63,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -83,7 +83,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
expect(page.find('.build-link')).not_to have_content(build4.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -95,7 +95,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -113,7 +113,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).to have_content(build3.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -125,7 +125,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No jobs to show'
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
end
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index 241c7cbc34e..cb96830cb7c 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -17,6 +17,16 @@ RSpec.describe 'admin deploy keys' do
end
end
+ it 'shows all the projects the deploy key has write access' do
+ write_key = create(:deploy_keys_project, :write_access, deploy_key: deploy_key)
+
+ visit admin_deploy_keys_path
+
+ page.within(find('.deploy-keys-list', match: :first)) do
+ expect(page).to have_content(write_key.project.full_name)
+ end
+ end
+
describe 'create a new deploy key' do
let(:new_ssh_key) { attributes_for(:key)[:key] }
@@ -28,14 +38,12 @@ RSpec.describe 'admin deploy keys' do
it 'creates a new deploy key' do
fill_in 'deploy_key_title', with: 'laptop'
fill_in 'deploy_key_key', with: new_ssh_key
- check 'deploy_key_can_push'
click_button 'Create'
expect(current_path).to eq admin_deploy_keys_path
page.within(find('.deploy-keys-list', match: :first)) do
expect(page).to have_content('laptop')
- expect(page).to have_content('Yes')
end
end
end
@@ -48,14 +56,12 @@ RSpec.describe 'admin deploy keys' do
it 'updates an existing deploy key' do
fill_in 'deploy_key_title', with: 'new-title'
- check 'deploy_key_can_push'
click_button 'Save changes'
expect(current_path).to eq admin_deploy_keys_path
page.within(find('.deploy-keys-list', match: :first)) do
expect(page).to have_content('new-title')
- expect(page).to have_content('Yes')
end
end
end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index eec44549a03..f266f2ecc54 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -1,11 +1,10 @@
require 'spec_helper'
-describe 'Admin::Hooks', :js do
- before do
- @project = create(:project)
- sign_in(create(:admin))
+describe 'Admin::Hooks' do
+ let(:user) { create(:admin) }
- @system_hook = create(:system_hook)
+ before do
+ sign_in(user)
end
describe 'GET /admin/hooks' do
@@ -13,15 +12,17 @@ describe 'Admin::Hooks', :js do
visit admin_root_path
page.within '.nav-sidebar' do
- click_on 'Hooks'
+ click_on 'System Hooks', match: :first
end
expect(current_path).to eq(admin_hooks_path)
end
it 'has hooks list' do
+ system_hook = create(:system_hook)
+
visit admin_hooks_path
- expect(page).to have_content(@system_hook.url)
+ expect(page).to have_content(system_hook.url)
end
end
@@ -43,6 +44,10 @@ describe 'Admin::Hooks', :js do
describe 'Update existing hook' do
let(:new_url) { generate(:url) }
+ before do
+ create(:system_hook)
+ end
+
it 'updates existing hook' do
visit admin_hooks_path
@@ -57,7 +62,11 @@ describe 'Admin::Hooks', :js do
end
end
- describe 'Remove existing hook' do
+ describe 'Remove existing hook', :js do
+ before do
+ create(:system_hook)
+ end
+
context 'removes existing hook' do
it 'from hooks list page' do
visit admin_hooks_path
@@ -76,7 +85,8 @@ describe 'Admin::Hooks', :js do
describe 'Test', :js do
before do
- WebMock.stub_request(:post, @system_hook.url)
+ system_hook = create(:system_hook)
+ WebMock.stub_request(:post, system_hook.url)
visit admin_hooks_path
find('.hook-test-button.dropdown').click
@@ -85,4 +95,41 @@ describe 'Admin::Hooks', :js do
it { expect(current_path).to eq(admin_hooks_path) }
end
+
+ context 'Merge request hook' do
+ describe 'New Hook' do
+ let(:url) { generate(:url) }
+
+ it 'adds new hook' do
+ visit admin_hooks_path
+
+ fill_in 'hook_url', with: url
+ uncheck 'Repository update events'
+ check 'Merge request events'
+
+ expect { click_button 'Add system hook' }.to change(SystemHook, :count).by(1)
+ expect(current_path).to eq(admin_hooks_path)
+ expect(page).to have_content(url)
+ end
+ end
+
+ describe 'Test', :js do
+ before do
+ system_hook = create(:system_hook)
+ WebMock.stub_request(:post, system_hook.url)
+ end
+
+ it 'succeeds if the user has a repository with a merge request' do
+ project = create(:project, :repository)
+ create(:project_member, user: user, project: project)
+ create(:merge_request, source_project: project)
+
+ visit admin_hooks_path
+ find('.hook-test-button.dropdown').click
+ click_link 'Merge requests events'
+
+ expect(page).to have_content 'Hook executed successfully'
+ end
+ end
+ end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 3876d1c76d7..3d13f806b11 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -69,6 +69,7 @@ describe 'Issue Boards', :js do
let!(:backlog) { create(:label, project: project, name: 'Backlog') }
let!(:closed) { create(:label, project: project, name: 'Closed') }
let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
+ let!(:a_plus) { create(:label, project: project, name: 'A+') }
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
let!(:list2) { create(:list, board: board, label: development, position: 1) }
@@ -83,6 +84,7 @@ describe 'Issue Boards', :js do
let!(:issue7) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
let!(:issue8) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
let!(:issue9) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
+ let!(:issue10) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
before do
visit project_board_path(project, board)
@@ -400,6 +402,15 @@ describe 'Issue Boards', :js do
wait_for_empty_boards((3..4))
end
+ it 'filters by label with encoded character' do
+ set_filter("label", a_plus.title)
+ click_filter_link(a_plus.title)
+ submit_filter
+
+ wait_for_board_cards(1, 1)
+ wait_for_empty_boards((2..4))
+ end
+
it 'filters by label with space after reload' do
set_filter("label", "\"#{accepting.title}")
click_filter_link(accepting.title)
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 205900615c4..b2dbfcd0031 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -334,14 +334,14 @@ describe 'Issue Boards', :js do
wait_for_requests
page.within('.subscriptions') do
- click_button 'Subscribe'
+ find('.js-issuable-subscribe-button button:not(.is-checked)').click
wait_for_requests
- expect(page).to have_content('Unsubscribe')
+ expect(page).to have_css('.js-issuable-subscribe-button button.is-checked')
end
end
- it 'has "Unsubscribe" button when already subscribed' do
+ it 'has checked subscription toggle when already subscribed' do
create(:subscription, user: user, project: project, subscribable: issue2, subscribed: true)
visit project_board_path(project, board)
wait_for_requests
@@ -350,10 +350,10 @@ describe 'Issue Boards', :js do
wait_for_requests
page.within('.subscriptions') do
- click_button 'Unsubscribe'
+ find('.js-issuable-subscribe-button button.is-checked').click
wait_for_requests
- expect(page).to have_content('Subscribe')
+ expect(page).to have_css('.js-issuable-subscribe-button button:not(.is-checked)')
end
end
end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index d8f1a919522..f82ed6300cc 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -750,7 +750,7 @@ describe 'Copy as GFM', :js do
js = <<-JS.strip_heredoc
(function(selector) {
var els = document.querySelectorAll(selector);
- var htmls = _.map(els, function(el) { return el.outerHTML; });
+ var htmls = [].slice.call(els).map(function(el) { return el.outerHTML; });
return htmls.join("\\n");
})("#{escape_javascript(selector)}")
JS
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index d36954954b6..510677ecf56 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -113,6 +113,7 @@ feature 'Cycle Analytics', :js do
context "as a guest" do
before do
+ project.add_developer(user)
project.add_guest(guest)
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index a5c9d0bde5d..64b4f9e7e67 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -8,6 +8,7 @@ feature 'Issue Sidebar' do
let(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
let!(:label) { create(:label, project: project, title: 'bug') }
+ let!(:xss_label) { create(:label, project: project, title: '&lt;script&gt;alert("xss");&lt;&#x2F;script&gt;') }
before do
sign_in(user)
@@ -99,6 +100,14 @@ feature 'Issue Sidebar' do
restore_window_size
open_issue_sidebar
end
+
+ it 'escapes XSS when viewing issue labels' do
+ page.within('.block.labels') do
+ find('.edit-link').click
+
+ expect(page).to have_content '<script>alert("xss");</script>'
+ end
+ end
end
context 'editing issue labels', :js do
diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb
new file mode 100644
index 00000000000..b6b38186a22
--- /dev/null
+++ b/spec/features/merge_request/user_assigns_themselves_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+describe 'Merge request > User assigns themselves' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:issue1) { create(:issue, project: project) }
+ let(:issue2) { create(:issue, project: project) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue1.to_reference} and #{issue2.to_reference}") }
+
+ context 'logged in as a member of the project' do
+ before do
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'updates related issues', :js do
+ click_link 'Assign yourself to these issues'
+
+ expect(page).to have_content '2 issues have been assigned to you'
+ end
+
+ it 'returns user to the merge request', :js do
+ click_link 'Assign yourself to these issues'
+
+ expect(page).to have_content merge_request.description
+ end
+
+ context 'when related issues are already assigned' do
+ before do
+ [issue1, issue2].each { |issue| issue.update!(assignees: [user]) }
+ end
+
+ it 'does not display if related issues are already assigned' do
+ expect(page).not_to have_content 'Assign yourself'
+ end
+ end
+ end
+
+ context 'logged in as a non-member of the project' do
+ before do
+ sign_in(create(:user))
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not not show assignment link' do
+ expect(page).not_to have_content 'Assign yourself'
+ end
+ end
+end
diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index a24464f2556..15a0878fb16 100644
--- a/spec/features/merge_requests/award_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -1,8 +1,8 @@
require 'rails_helper'
-feature 'Merge request awards', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User awards emoji', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project) }
describe 'logged in' do
diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_request/user_cherry_picks_spec.rb
index 205e38337d1..494096b21c0 100644
--- a/spec/features/merge_requests/cherry_pick_spec.rb
+++ b/spec/features/merge_request/user_cherry_picks_spec.rb
@@ -1,17 +1,17 @@
-require 'spec_helper'
+require 'rails_helper'
-describe 'Cherry-pick Merge Requests', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User cherry-picks', :js do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) }
before do
- sign_in user
project.add_master(user)
+ sign_in(user)
end
- context "Viewing a merged merge request" do
+ context 'Viewing a merged merge request' do
before do
service = MergeRequests::MergeService.new(project, user)
@@ -21,24 +21,24 @@ describe 'Cherry-pick Merge Requests', :js do
end
# Fast-forward merge, or merged before GitLab 8.5.
- context "Without a merge commit" do
+ context 'Without a merge commit' do
before do
merge_request.merge_commit_sha = nil
merge_request.save
end
- it "doesn't show a Cherry-pick button" do
+ it 'does not show a Cherry-pick button' do
visit project_merge_request_path(project, merge_request)
- expect(page).not_to have_link "Cherry-pick"
+ expect(page).not_to have_link 'Cherry-pick'
end
end
- context "With a merge commit" do
- it "shows a Cherry-pick button" do
+ context 'With a merge commit' do
+ it 'shows a Cherry-pick button' do
visit project_merge_request_path(project, merge_request)
- expect(page).to have_link "Cherry-pick"
+ expect(page).to have_link 'Cherry-pick'
end
end
end
diff --git a/spec/features/merge_requests/image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index d0f8da4e6cd..7c4fd25bb39 100644
--- a/spec/features/merge_requests/image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -1,14 +1,13 @@
require 'spec_helper'
-feature 'image diff notes', :js do
+feature 'Merge request > User creates image diff notes', :js do
include NoteInteractionHelpers
- let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
before do
- project.add_master(user)
- sign_in user
+ sign_in(user)
# Stub helper to return any blob file as image from public app folder.
# This is necessary to run this specs since we don't display repo images in capybara.
@@ -25,20 +24,14 @@ feature 'image diff notes', :js do
create_image_diff_note
end
- it 'shows indicator badge on image diff' do
+ it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes' do
indicator = find('.js-image-badge')
-
- expect(indicator).to have_content('1')
- end
-
- it 'shows the avatar badge on the new note' do
badge = find('.image-diff-avatar-link .badge')
+ expect(indicator).to have_content('1')
expect(badge).to have_content('1')
- end
- it 'allows collapsing/expanding the discussion notes' do
- find('.js-diff-notes-toggle', :first).click
+ find('.js-diff-notes-toggle').click
expect(page).not_to have_content('image diff test comment')
@@ -86,15 +79,9 @@ feature 'image diff notes', :js do
wait_for_requests
end
- it 'render diff indicators within the image diff frame' do
+ it 'render diff indicators within the image diff frame, diff notes, and avatar badge numbers' do
expect(page).to have_css('.js-image-badge', count: 2)
- end
-
- it 'shows the diff notes' do
expect(page).to have_css('.diff-content .note', count: 2)
- end
-
- it 'shows the diff notes with correct avatar badge numbers' do
expect(page).to have_css('.image-diff-avatar-link', text: 1)
expect(page).to have_css('.image-diff-avatar-link', text: 2)
end
@@ -127,19 +114,13 @@ feature 'image diff notes', :js do
create_image_diff_note
end
- it 'shows indicator badge on image diff' do
+ it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes' do
indicator = find('.js-image-badge', match: :first)
-
- expect(indicator).to have_content('1')
- end
-
- it 'shows the avatar badge on the new note' do
badge = find('.image-diff-avatar-link .badge', match: :first)
+ expect(indicator).to have_content('1')
expect(badge).to have_content('1')
- end
- it 'allows expanding/collapsing the discussion notes' do
page.all('.js-diff-notes-toggle')[0].click
page.all('.js-diff-notes-toggle')[1].click
@@ -154,7 +135,7 @@ feature 'image diff notes', :js do
end
end
- describe 'discussion tab polling', :js do
+ describe 'discussion tab polling' do
let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) }
let(:path) { "files/images/ee_repo_logo.png" }
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
new file mode 100644
index 00000000000..1ac31de62cb
--- /dev/null
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+
+describe 'Merge request > User creates MR' do
+ it_behaves_like 'a creatable merge request'
+
+ context 'from a forked project' do
+ include ProjectForksHelper
+
+ let(:canonical_project) { create(:project, :public, :repository) }
+
+ let(:source_project) do
+ fork_project(canonical_project, user,
+ repository: true,
+ namespace: user.namespace)
+ end
+
+ context 'to canonical project' do
+ it_behaves_like 'a creatable merge request'
+ end
+
+ context 'to another forked project' do
+ let(:target_project) do
+ fork_project(canonical_project, user,
+ repository: true,
+ namespace: user.namespace)
+ end
+
+ it_behaves_like 'a creatable merge request'
+ end
+ end
+end
diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
index ddd034e1376..e1e70b6d260 100644
--- a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
+++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
@@ -1,8 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Clicking toggle commit message link', :js do
- let(:user) { create(:user) }
+describe 'Merge request < User customizes merge commit message', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:issue_1) { create(:issue, project: project)}
let(:issue_2) { create(:issue, project: project)}
let(:merge_request) do
@@ -33,17 +33,14 @@ feature 'Clicking toggle commit message link', :js do
before do
project.add_master(user)
-
- sign_in user
-
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
+ end
+ it 'toggles commit message between message with description and without description' do
expect(page).not_to have_selector('.js-commit-message')
click_button "Modify commit message"
expect(textbox).to be_visible
- end
-
- it "toggles commit message between message with description and without description " do
expect(textbox.value).to eq(default_message)
click_link "Include description in commit message"
diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb
new file mode 100644
index 00000000000..8c9e782aa76
--- /dev/null
+++ b/spec/features/merge_request/user_edits_mr_spec.rb
@@ -0,0 +1,11 @@
+require 'rails_helper'
+
+describe 'Merge request > User edits MR' do
+ it_behaves_like 'an editable merge request'
+
+ context 'for a forked project' do
+ it_behaves_like 'an editable merge request' do
+ let(:source_project) { create(:project, :repository, forked_from_project: target_project) }
+ end
+ end
+end
diff --git a/spec/features/merge_requests/discussion_lock_spec.rb b/spec/features/merge_request/user_locks_discussion_spec.rb
index 7bbd3b1e69e..a68df872334 100644
--- a/spec/features/merge_requests/discussion_lock_spec.rb
+++ b/spec/features/merge_request/user_locks_discussion_spec.rb
@@ -1,9 +1,9 @@
-require 'spec_helper'
+require 'rails_helper'
-describe 'Discussion Lock', :js do
+describe 'Merge request > User locks discussion', :js do
let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request, source_project: project, author: user) }
let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
before do
sign_in(user)
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb
index e1317b33ad1..b16fc9bfc89 100644
--- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
+++ b/spec/features/merge_request/user_merges_immediately_spec.rb
@@ -1,9 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge immediately', :js do
- let(:user) { create(:user) }
+describe 'Merge requests > User merges immediately', :js do
let(:project) { create(:project, :public, :repository) }
-
+ let(:user) { project.creator }
let!(:merge_request) do
create(:merge_request_with_diffs, source_project: project,
author: user,
@@ -11,25 +10,18 @@ feature 'Merge immediately', :js do
head_pipeline: pipeline,
source_branch: pipeline.ref)
end
-
let(:pipeline) do
create(:ci_pipeline, project: project,
ref: 'master',
sha: project.repository.commit('master').id)
end
- before do
- project.add_master(user)
- end
-
context 'when there is active pipeline for merge request' do
- background do
- create(:ci_build, pipeline: pipeline)
- end
-
before do
- sign_in user
- visit project_merge_request_path(merge_request.project, merge_request)
+ create(:ci_build, pipeline: pipeline)
+ project.add_master(user)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
it 'enables merge immediately' do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
index 7d9282b932b..a045791f6b4 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
@@ -1,18 +1,17 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Only allow merge requests to be merged if the pipeline succeeds', :js do
+describe 'Merge request > User merges only if pipeline succeeds', :js do
let(:merge_request) { create(:merge_request_with_diffs) }
let(:project) { merge_request.target_project }
before do
- sign_in merge_request.author
-
project.add_master(merge_request.author)
+ sign_in(merge_request.author)
end
- context 'project does not have CI enabled', :js do
+ context 'project does not have CI enabled' do
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -20,8 +19,8 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
end
- context 'when project has CI enabled', :js do
- given!(:pipeline) do
+ context 'when project has CI enabled' do
+ let!(:pipeline) do
create(:ci_empty_pipeline,
project: project,
sha: merge_request.diff_head_sha,
@@ -35,10 +34,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI is running' do
- given(:status) { :running }
+ let(:status) { :running }
it 'does not allow to merge immediately' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -48,10 +47,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI failed' do
- given(:status) { :failed }
+ let(:status) { :failed }
it 'does not allow MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -61,10 +60,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI canceled' do
- given(:status) { :canceled }
+ let(:status) { :canceled }
it 'does not allow MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -74,10 +73,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI succeeded' do
- given(:status) { :success }
+ let(:status) { :success }
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -86,10 +85,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI skipped' do
- given(:status) { :skipped }
+ let(:status) { :skipped }
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -104,10 +103,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI is running' do
- given(:status) { :running }
+ let(:status) { :running }
it 'allows MR to be merged immediately' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -119,10 +118,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI failed' do
- given(:status) { :failed }
+ let(:status) { :failed }
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -131,10 +130,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI succeeded' do
- given(:status) { :success }
+ let(:status) { :success }
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -143,8 +142,4 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
end
end
-
- def visit_merge_request(merge_request)
- visit project_merge_request_path(merge_request.project, merge_request)
- end
end
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index ac46cc1f0e4..890774922aa 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -1,16 +1,14 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge When Pipeline Succeeds', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User merges when pipeline succeeds', :js do
let(:project) { create(:project, :public, :repository) }
-
+ let(:user) { project.creator }
let(:merge_request) do
create(:merge_request_with_diffs, source_project: project,
author: user,
title: 'Bug NS-04',
merge_params: { force_remove_source_branch: '1' })
end
-
let(:pipeline) do
create(:ci_pipeline, project: project,
sha: merge_request.diff_head_sha,
@@ -23,17 +21,10 @@ feature 'Merge When Pipeline Succeeds', :js do
end
context 'when there is active pipeline for merge request' do
- background do
- create(:ci_build, pipeline: pipeline)
- end
-
before do
- sign_in user
- visit_merge_request(merge_request)
- end
-
- it 'displays the Merge when pipeline succeeds button' do
- expect(page).to have_button "Merge when pipeline succeeds"
+ create(:ci_build, pipeline: pipeline)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
describe 'enabling Merge when pipeline succeeds' do
@@ -44,7 +35,7 @@ feature 'Merge When Pipeline Succeeds', :js do
expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds"
expect(page).to have_content "The source branch will not be removed"
expect(page).to have_selector ".js-cancel-auto-merge"
- visit_merge_request(merge_request) # Needed to refresh the page
+ visit project_merge_request_path(project, merge_request) # Needed to refresh the page
expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
end
end
@@ -115,14 +106,13 @@ feature 'Merge When Pipeline Succeeds', :js do
title: 'MepMep',
merge_when_pipeline_succeeds: true)
end
-
let!(:build) do
create(:ci_build, pipeline: pipeline)
end
before do
sign_in user
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'allows to cancel the automatic merge' do
@@ -130,31 +120,67 @@ feature 'Merge When Pipeline Succeeds', :js do
expect(page).to have_button "Merge when pipeline succeeds"
- visit_merge_request(merge_request) # refresh the page
+ refresh
+
expect(page).to have_content "canceled the automatic merge"
end
context 'when pipeline succeeds' do
- background { build.success }
+ before do
+ build.success
+ refresh
+ end
it 'merges merge request' do
- visit_merge_request(merge_request) # refresh the page
-
expect(page).to have_content 'The changes were merged'
expect(merge_request.reload).to be_merged
end
end
+
+ context 'view merge request with MWPS enabled but automatically merge fails' do
+ before do
+ merge_request.update(
+ merge_user: merge_request.author,
+ merge_error: 'Something went wrong'
+ )
+ refresh
+ end
+
+ it 'shows information about the merge error' do
+ # Wait for the `ci_status` and `merge_check` requests
+ wait_for_requests
+
+ page.within('.mr-widget-body') do
+ expect(page).to have_content('Something went wrong')
+ end
+ end
+ end
+
+ context 'view merge request with MWPS enabled but automatically merge fails' do
+ before do
+ merge_request.update(
+ merge_user: merge_request.author,
+ merge_error: 'Something went wrong'
+ )
+ refresh
+ end
+
+ it 'shows information about the merge error' do
+ # Wait for the `ci_status` and `merge_check` requests
+ wait_for_requests
+
+ page.within('.mr-widget-body') do
+ expect(page).to have_content('Something went wrong')
+ end
+ end
+ end
end
context 'when pipeline is not active' do
- it "does not allow to enable merge when pipeline succeeds" do
- visit_merge_request(merge_request)
+ it 'does not allow to enable merge when pipeline succeeds' do
+ visit project_merge_request_path(project, merge_request)
expect(page).not_to have_link 'Merge when pipeline succeeds'
end
end
-
- def visit_merge_request(merge_request)
- visit project_merge_request_path(merge_request.project, merge_request)
- end
end
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index d44eb23d7f4..2b4623d6dc9 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -1,11 +1,15 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge requests > User posts diff notes', :js do
+describe 'Merge request > User posts diff notes', :js do
include MergeRequestDiffHelpers
- let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
+ let(:user) { project.creator }
+ let(:comment_button_class) { '.add-diff-note' }
+ let(:notes_holder_input_class) { 'js-temp-notes-holder' }
+ let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
+ let(:test_note_comment) { 'this is a test note!' }
before do
set_cookie('sidebar_collapsed', 'true')
@@ -14,11 +18,6 @@ feature 'Merge requests > User posts diff notes', :js do
sign_in(user)
end
- let(:comment_button_class) { '.add-diff-note' }
- let(:notes_holder_input_class) { 'js-temp-notes-holder' }
- let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
- let(:test_note_comment) { 'this is a test note!' }
-
context 'when hovering over a parallel view diff file' do
before do
visit diffs_project_merge_request_path(project, merge_request, view: 'parallel')
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index e17e9c2ccf5..50d06565fc0 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -1,9 +1,10 @@
-require 'spec_helper'
+require 'rails_helper'
-describe 'Merge requests > User posts notes', :js do
+describe 'Merge request > User posts notes', :js do
include NoteInteractionHelpers
let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
let(:merge_request) do
create(:merge_request, source_project: project, target_project: project)
end
@@ -13,7 +14,8 @@ describe 'Merge requests > User posts notes', :js do
end
before do
- sign_in(create(:admin))
+ project.add_master(user)
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index 05d99a2dff2..61861d33952 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -1,8 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge request conflict resolution', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User resolves conflicts', :js do
let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
before do
# In order to have the diffs collapsed, we need to disable the increase feature
@@ -177,7 +177,6 @@ feature 'Merge request conflict resolution', :js do
before do
project.add_developer(user)
sign_in(user)
-
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 9d4194d8ca0..590210d44ef 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -1,10 +1,11 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Diff notes resolve', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User resolves diff notes and discussions', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:guest) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") }
- let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
+ let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
let(:path) { "files/ruby/popen.rb" }
let(:position) do
Gitlab::Diff::Position.new(
@@ -19,7 +20,7 @@ feature 'Diff notes resolve', :js do
context 'no discussions' do
before do
project.add_master(user)
- sign_in user
+ sign_in(user)
note.destroy
visit_merge_request
end
@@ -33,7 +34,7 @@ feature 'Diff notes resolve', :js do
context 'as authorized user' do
before do
project.add_master(user)
- sign_in user
+ sign_in(user)
visit_merge_request
end
@@ -67,6 +68,8 @@ feature 'Diff notes resolve', :js do
click_button 'Resolve discussion'
end
+ expect(page).to have_selector('.discussion-body', visible: false)
+
page.within '.diff-content .note' do
expect(page).to have_selector('.line-resolve-btn.is-active')
end
@@ -318,9 +321,7 @@ feature 'Diff notes resolve', :js do
end
it 'shows jump to next discussion button' do
- page.all('.discussion-reply-holder').each do |holder|
- expect(holder).to have_selector('.discussion-next-btn')
- end
+ expect(page.all('.discussion-reply-holder')).to all(have_selector('.discussion-next-btn'))
end
it 'displays next discussion even if hidden' do
@@ -426,11 +427,9 @@ feature 'Diff notes resolve', :js do
end
context 'as a guest' do
- let(:guest) { create(:user) }
-
before do
project.add_guest(guest)
- sign_in guest
+ sign_in(guest)
end
context 'someone elses merge request' do
@@ -456,10 +455,10 @@ feature 'Diff notes resolve', :js do
end
context 'guest users merge request' do
+ let(:user) { guest }
+
before do
- mr = create(:merge_request_with_diffs, source_project: project, source_branch: 'markdown', author: guest, title: "Bug")
- create(:diff_note_on_merge_request, project: project, noteable: mr)
- visit_merge_request(mr)
+ visit_merge_request
end
it 'allows user to mark a note as resolved' do
@@ -521,7 +520,7 @@ feature 'Diff notes resolve', :js do
end
def visit_merge_request(mr = nil)
- mr = mr || merge_request
+ mr ||= merge_request
visit project_merge_request_path(mr.project, mr)
end
end
diff --git a/spec/features/merge_requests/resolve_outdated_diff_discussions.rb b/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
index 25abbb469ab..9ba9e8b9585 100644
--- a/spec/features/merge_requests/resolve_outdated_diff_discussions.rb
+++ b/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Resolve outdated diff discussions', :js do
+feature 'Merge request > User resolves outdated diff discussions', :js do
let(:project) { create(:project, :repository, :public) }
let(:merge_request) do
diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
new file mode 100644
index 00000000000..8a834adbf17
--- /dev/null
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+describe 'Merge request > User scrolls to note on load', :js do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:merge_request) { create(:merge_request, source_project: project, author: user) }
+ let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
+ let(:fragment_id) { "#note_#{note.id}" }
+
+ before do
+ sign_in(user)
+ page.current_window.resize_to(1000, 300)
+ visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
+ end
+
+ it 'scrolls down to fragment' do
+ page_height = page.current_window.size[1]
+ page_scroll_y = page.evaluate_script("window.scrollY")
+ fragment_position_top = page.evaluate_script("Math.round($('#{fragment_id}').offset().top)")
+
+ expect(find('.js-toggle-content').visible?).to eq true
+ expect(find(fragment_id).visible?).to eq true
+ expect(fragment_position_top).to be >= page_scroll_y
+ expect(fragment_position_top).to be < (page_scroll_y + page_height)
+ end
+end
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index ef8f314cc03..9c0a04405a6 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -1,10 +1,10 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Diff note avatars', :js do
+describe 'Merge request > User sees avatars on diff notes', :js do
include NoteInteractionHelpers
- let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") }
let(:path) { "files/ruby/popen.rb" }
let(:position) do
@@ -151,7 +151,6 @@ feature 'Diff note avatars', :js do
page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test')
-
find('.js-comment-button').click
wait_for_requests
@@ -169,7 +168,6 @@ feature 'Diff note avatars', :js do
context 'multiple comments' do
before do
create_list(:diff_note_on_merge_request, 3, project: project, noteable: merge_request, in_reply_to: note)
-
visit diffs_project_merge_request_path(project, merge_request, view: view)
wait_for_requests
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
index 55de9a01ed5..726f35557a7 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
@@ -1,8 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge Request closing issues message', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User sees closing issues message', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:issue_1) { create(:issue, project: project)}
let(:issue_2) { create(:issue, project: project)}
let(:merge_request) do
@@ -19,9 +19,7 @@ feature 'Merge Request closing issues message', :js do
before do
project.add_master(user)
-
- sign_in user
-
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
wait_for_requests
end
diff --git a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
new file mode 100644
index 00000000000..01115318370
--- /dev/null
+++ b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees deleted target branch', :js do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:user) { project.creator }
+
+ before do
+ project.add_master(user)
+ DeleteBranchService.new(project, user).execute('feature')
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows a message about missing target branch' do
+ expect(page).to have_content('Target branch does not exist')
+ end
+
+ it 'does not show link to target branch' do
+ expect(page).not_to have_selector('.mr-widget-body .js-branch-text a')
+ end
+end
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
new file mode 100644
index 00000000000..3abe363d523
--- /dev/null
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -0,0 +1,56 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees deployment widget', :js do
+ describe 'when deployed to an environment' do
+ let(:user) { create(:user) }
+ let(:project) { merge_request.target_project }
+ let(:merge_request) { create(:merge_request, :merged) }
+ let(:environment) { create(:environment, project: project) }
+ let(:role) { :developer }
+ let(:sha) { project.commit('master').id }
+ let!(:deployment) { create(:deployment, environment: environment, sha: sha) }
+ let!(:manual) { }
+
+ before do
+ project.add_user(user, role)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ wait_for_requests
+ end
+
+ it 'displays that the environment is deployed' do
+ wait_for_requests
+
+ expect(page).to have_content("Deployed to #{environment.name}")
+ expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
+ end
+
+ context 'with stop action' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
+ let(:deployment) do
+ create(:deployment, environment: environment, ref: merge_request.target_branch,
+ sha: sha, deployable: build, on_stop: 'close_app')
+ end
+
+ before do
+ wait_for_requests
+ end
+
+ it 'does start build when stop button clicked' do
+ accept_confirm { click_button('Stop environment') }
+
+ expect(page).to have_content('close_app')
+ end
+
+ context 'for reporter' do
+ let(:role) { :reporter }
+
+ it 'does not show stop button' do
+ expect(page).not_to have_button('Stop environment')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 1bf77296ae6..a9063f2bcb3 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -1,6 +1,6 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Diffs URL', :js do
+describe 'Merge request > User sees diff', :js do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb
index 05789bbd31d..d6e8c8e86ba 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_spec.rb
@@ -1,19 +1,20 @@
-require 'spec_helper'
+require 'rails_helper'
+
+describe 'Merge request > User sees discussions' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:merge_request) { create(:merge_request, source_project: project) }
-feature 'Merge Request Discussions' do
before do
- sign_in(create(:admin))
+ project.add_master(user)
+ sign_in(user)
end
describe "Diff discussions" do
- let(:merge_request) { create(:merge_request, importing: true) }
- let(:project) { merge_request.source_project }
let!(:old_merge_request_diff) { merge_request.merge_request_diffs.create(diff_refs: outdated_diff_refs) }
let!(:new_merge_request_diff) { merge_request.merge_request_diffs.create }
-
let!(:outdated_discussion) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: outdated_position).to_discussion }
let!(:active_discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
-
let(:outdated_position) do
Gitlab::Diff::Position.new(
old_path: "files/ruby/popen.rb",
@@ -23,7 +24,6 @@ feature 'Merge Request Discussions' do
diff_refs: outdated_diff_refs
)
end
-
let(:outdated_diff_refs) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e").diff_refs }
before do
@@ -50,9 +50,6 @@ feature 'Merge Request Discussions' do
end
describe 'Commit comments displayed in MR context', :js do
- let(:merge_request) { create(:merge_request) }
- let(:project) { merge_request.project }
-
shared_examples 'a functional discussion' do
let(:discussion_id) { note.discussion_id(merge_request) }
diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_request/user_sees_empty_state_spec.rb
new file mode 100644
index 00000000000..a939c7e9001
--- /dev/null
+++ b/spec/features/merge_request/user_sees_empty_state_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees empty state' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'shows an empty state and a "New merge request" button' do
+ visit project_merge_requests_path(project)
+
+ expect(page).to have_selector('.empty-state')
+ expect(page).to have_link 'New merge request', href: project_new_merge_request_path(project)
+ end
+
+ context 'if there are merge requests' do
+ before do
+ create(:merge_request, source_project: project)
+
+ visit project_merge_requests_path(project)
+ end
+
+ it 'does not show an empty state' do
+ expect(page).not_to have_selector('.empty-state')
+ end
+ end
+end
diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
index 892c32c8806..85df43df38e 100644
--- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
@@ -1,24 +1,23 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Check if mergeable with unresolved discussions', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User sees merge button depending on unresolved discussions', :js do
let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
before do
- sign_in user
project.add_master(user)
+ sign_in(user)
end
context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do
before do
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true)
+ visit project_merge_request_path(project, merge_request)
end
context 'with unresolved discussions' do
it 'does not allow to merge' do
- visit_merge_request(merge_request)
-
expect(page).not_to have_button 'Merge'
expect(page).to have_content('There are unresolved discussions.')
end
@@ -27,11 +26,10 @@ feature 'Check if mergeable with unresolved discussions', :js do
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
+ visit project_merge_request_path(project, merge_request)
end
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
-
expect(page).to have_button 'Merge'
end
end
@@ -40,12 +38,11 @@ feature 'Check if mergeable with unresolved discussions', :js do
context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do
before do
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false)
+ visit project_merge_request_path(project, merge_request)
end
context 'with unresolved discussions' do
it 'does not allow to merge' do
- visit_merge_request(merge_request)
-
expect(page).to have_button 'Merge'
end
end
@@ -53,17 +50,12 @@ feature 'Check if mergeable with unresolved discussions', :js do
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
+ visit project_merge_request_path(project, merge_request)
end
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
-
expect(page).to have_button 'Merge'
end
end
end
-
- def visit_merge_request(merge_request)
- visit project_merge_request_path(merge_request.project, merge_request)
- end
end
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 8970586a160..56224e505d9 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -1,9 +1,9 @@
require 'rails_helper'
-describe 'Merge request', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User sees merge widget', :js do
let(:project) { create(:project, :repository) }
let(:project_only_mwps) { create(:project, :repository, only_allow_merge_if_pipeline_succeeds: true) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project) }
let(:merge_request_in_only_mwps_project) { create(:merge_request, source_project: project_only_mwps) }
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index a7e7c0eeff6..a43ba05c64c 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -1,16 +1,14 @@
require 'rails_helper'
-feature 'Mini Pipeline Graph', :js do
- let(:user) { create(:user) }
+describe 'Merge request < User sees mini pipeline graph', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project, head_pipeline: pipeline) }
-
let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running', sha: project.commit.id) }
let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') }
before do
build.run
-
sign_in(user)
visit_merge_request
end
@@ -19,13 +17,13 @@ feature 'Mini Pipeline Graph', :js do
visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
end
- it 'should display a mini pipeline graph' do
+ it 'displays a mini pipeline graph' do
expect(page).to have_selector('.mr-widget-pipeline-graph')
end
context 'as json' do
- let(:artifacts_file1) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
- let(:artifacts_file2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') }
+ let(:artifacts_file1) { fixture_file_upload(Rails.root.join('spec/fixtures/banana_sample.gif'), 'image/gif') }
+ let(:artifacts_file2) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
before do
create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
@@ -51,7 +49,7 @@ feature 'Mini Pipeline Graph', :js do
first('.mini-pipeline-graph-dropdown-toggle')
end
- it 'should expand when hovered' do
+ it 'expands when hovered' do
find('.mini-pipeline-graph-dropdown-toggle')
before_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();")
@@ -63,13 +61,13 @@ feature 'Mini Pipeline Graph', :js do
expect(before_width).to be < after_width
end
- it 'should show dropdown caret when hovered' do
+ it 'shows dropdown caret when hovered' do
toggle.hover
expect(toggle).to have_selector('.fa-caret-down')
end
- it 'should show tooltip when hovered' do
+ it 'shows tooltip when hovered' do
toggle.hover
expect(page).to have_selector('.tooltip')
@@ -87,17 +85,17 @@ feature 'Mini Pipeline Graph', :js do
wait_for_requests
end
- it 'should open when toggle is clicked' do
+ it 'pens when toggle is clicked' do
expect(toggle.find(:xpath, '..')).to have_selector('.mini-pipeline-graph-dropdown-menu')
end
- it 'should close when toggle is clicked again' do
+ it 'closes when toggle is clicked again' do
toggle.click
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
end
- it 'should close when clicking somewhere else' do
+ it 'closes when clicking somewhere else' do
find('body').click
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
@@ -109,14 +107,14 @@ feature 'Mini Pipeline Graph', :js do
first('.mini-pipeline-graph-dropdown-item')
end
- it 'should visit the build page when clicked' do
+ it 'visits the build page when clicked' do
build_item.click
find('.build-page')
expect(current_path).to eql(project_job_path(project, build))
end
- it 'should show tooltip when hovered' do
+ it 'shows tooltip when hovered' do
build_item.hover
expect(page).to have_selector('.tooltip')
diff --git a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
new file mode 100644
index 00000000000..029b66b5e8e
--- /dev/null
+++ b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees MR from deleted forked project', :js do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:fork_project) { create(:project, :public, :repository, forked_from_project: project) }
+ let!(:merge_request) do
+ create(:merge_request_with_diffs, source_project: fork_project,
+ target_project: project,
+ description: 'Test merge request')
+ end
+
+ before do
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ fork_project.destroy!
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'user can access merge request' do
+ expect(page).to have_content 'Test merge request'
+ expect(page).to have_content "(removed):#{merge_request.source_branch}"
+ end
+end
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
index 56aa0b2ede2..c1608be402a 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
@@ -1,23 +1,21 @@
-require 'spec_helper'
+require 'rails_helper'
# This test serves as a regression test for a bug that caused an error
# message to be shown by JavaScript when the source branch was deleted.
-# Please do not remove "js: true".
-describe 'Deleted source branch', :js do
- let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request) }
+# Please do not remove ":js".
+describe 'Merge request > User sees MR with deleted source branch', :js do
+ let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:user) { project.creator }
before do
- sign_in user
- merge_request.project.add_master(user)
merge_request.update!(source_branch: 'this-branch-does-not-exist')
- visit project_merge_request_path(merge_request.project, merge_request)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows a message about missing source branch' do
- expect(page).to have_content(
- 'Source branch does not exist.'
- )
+ expect(page).to have_content('Source branch does not exist.')
end
it 'still contains Discussion, Commits and Changes tabs' do
diff --git a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
new file mode 100644
index 00000000000..b4cda269852
--- /dev/null
+++ b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
@@ -0,0 +1,35 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees notes from forked project', :js do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:fork_project) { create(:project, :public, :repository, forked_from_project: project) }
+ let!(:merge_request) do
+ create(:merge_request_with_diffs, source_project: fork_project,
+ target_project: project,
+ description: 'Test merge request')
+ end
+
+ before do
+ create(:note_on_commit, note: 'A commit comment',
+ project: fork_project,
+ commit_id: merge_request.commit_shas.first)
+ sign_in(user)
+ end
+
+ it 'user can reply to the comment' do
+ visit project_merge_request_path(project, merge_request)
+
+ expect(page).to have_content('A commit comment')
+
+ page.within('.discussion-notes') do
+ find('.btn-text-field').click
+ find('#note_note').send_keys('A reply comment')
+ find('.comment-btn').click
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content('A reply comment')
+ end
+end
diff --git a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
new file mode 100644
index 00000000000..d30dcefc6aa
--- /dev/null
+++ b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
@@ -0,0 +1,34 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees pipelines from forked project', :js do
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:user) { target_project.creator }
+ let(:fork_project) { create(:project, :repository, forked_from_project: target_project) }
+ let!(:merge_request) do
+ create(:merge_request_with_diffs, source_project: fork_project,
+ target_project: target_project,
+ description: 'Test merge request')
+ end
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: fork_project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch)
+ end
+
+ before do
+ create(:ci_build, pipeline: pipeline, name: 'rspec')
+ create(:ci_build, pipeline: pipeline, name: 'spinach')
+
+ sign_in(user)
+ visit project_merge_request_path(target_project, merge_request)
+ end
+
+ it 'user visits a pipelines page' do
+ page.within('.merge-request-tabs') { click_link 'Pipelines' }
+
+ page.within('.ci-table') do
+ expect(page).to have_content(pipeline.id)
+ end
+ end
+end
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index 04e3f4bdcf1..a42c016392b 100644
--- a/spec/features/merge_requests/pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -1,14 +1,14 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Pipelines for Merge Requests', :js do
+describe 'Merge request > User sees pipelines', :js do
describe 'pipeline tab' do
- given(:user) { create(:user) }
- given(:merge_request) { create(:merge_request) }
- given(:project) { merge_request.target_project }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.target_project }
+ let(:user) { project.creator }
before do
project.add_master(user)
- sign_in user
+ sign_in(user)
end
context 'with pipelines' do
@@ -23,7 +23,7 @@ feature 'Pipelines for Merge Requests', :js do
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
end
- scenario 'user visits merge request pipelines tab' do
+ it 'user visits merge request pipelines tab' do
visit project_merge_request_path(project, merge_request)
expect(page.find('.ci-widget')).to have_content('pending')
@@ -36,7 +36,7 @@ feature 'Pipelines for Merge Requests', :js do
expect(page).to have_selector('.stage-cell')
end
- scenario 'pipeline sha does not equal last commit sha' do
+ it 'pipeline sha does not equal last commit sha' do
pipeline.update_attribute(:sha, '19e2e9b4ef76b422ce1154af39a91323ccc57434')
visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -51,7 +51,7 @@ feature 'Pipelines for Merge Requests', :js do
visit project_merge_request_path(project, merge_request)
end
- scenario 'user visits merge request page' do
+ it 'user visits merge request page' do
page.within('.merge-request-tabs') do
expect(page).to have_no_link('Pipelines')
end
@@ -60,22 +60,22 @@ feature 'Pipelines for Merge Requests', :js do
end
describe 'race condition' do
- given(:project) { create(:project, :repository) }
- given(:user) { create(:user) }
- given(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } }
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } }
- given(:merge_request_params) do
+ let(:merge_request_params) do
{ "source_branch" => "feature", "source_project_id" => project.id,
"target_branch" => "master", "target_project_id" => project.id, "title" => "A" }
end
- background do
+ before do
project.add_master(user)
sign_in user
end
context 'when pipeline and merge request were created simultaneously' do
- background do
+ before do
stub_ci_pipeline_to_return_yaml_file
threads = []
@@ -91,7 +91,7 @@ feature 'Pipelines for Merge Requests', :js do
threads.each { |thr| thr.join }
end
- scenario 'user sees pipeline in merge request widget' do
+ it 'user sees pipeline in merge request widget' do
visit project_merge_request_path(project, @merge_request)
expect(page.find(".ci-widget")).to have_content(TestEnv::BRANCH_SHA['feature'])
diff --git a/spec/features/merge_requests/user_sees_system_notes_spec.rb b/spec/features/merge_request/user_sees_system_notes_spec.rb
index 03dc61c2efa..a00a682757d 100644
--- a/spec/features/merge_requests/user_sees_system_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_system_notes_spec.rb
@@ -1,15 +1,15 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge requests > User sees system notes' do
+describe 'Merge request > User sees system notes' do
let(:public_project) { create(:project, :public, :repository) }
let(:private_project) { create(:project, :private, :repository) }
+ let(:user) { private_project.creator }
let(:issue) { create(:issue, project: private_project) }
let(:merge_request) { create(:merge_request, source_project: public_project, source_branch: 'markdown') }
let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: public_project, note: "mentioned in #{issue.to_reference(public_project)}") }
context 'when logged-in as a member of the private project' do
before do
- user = create(:user)
private_project.add_developer(user)
sign_in(user)
end
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index 482f2e51c8b..3a15d70979a 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -1,15 +1,17 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge Request versions', :js do
+describe 'Merge request > User sees versions', :js do
let(:merge_request) { create(:merge_request, importing: true) }
let(:project) { merge_request.source_project }
+ let(:user) { project.creator }
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
let!(:merge_request_diff2) { merge_request.merge_request_diffs.create(head_commit_sha: nil) }
let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
- let!(:params) { Hash.new }
+ let!(:params) { {} }
before do
- sign_in(create(:admin))
+ project.add_master(user)
+ sign_in(user)
visit diffs_project_merge_request_path(project, merge_request, params)
end
@@ -62,19 +64,10 @@ feature 'Merge Request versions', :js do
end
end
- it 'should show older version' do
- page.within '.mr-version-dropdown' do
- expect(page).to have_content 'version 1'
- end
-
+ it 'shows comments that were last relevant at that version' do
expect(page).to have_content '5 changed files'
- end
-
- it 'show the message about comments' do
expect(page).to have_content 'Not all comments are displayed'
- end
- it 'shows comments that were last relevant at that version' do
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
@@ -86,7 +79,7 @@ feature 'Merge Request versions', :js do
outdated_diff_note.position = outdated_diff_note.original_position
outdated_diff_note.save!
- visit current_url
+ refresh
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
@@ -110,26 +103,16 @@ feature 'Merge Request versions', :js do
end
end
- it 'has a path with comparison context' do
+ it 'has a path with comparison context and shows comments that were last relevant at that version' do
expect(page).to have_current_path diffs_project_merge_request_path(
project,
merge_request.iid,
diff_id: merge_request_diff3.id,
start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
)
- end
-
- it 'should have correct value in the compare dropdown' do
- page.within '.mr-version-compare-dropdown' do
- expect(page).to have_content 'version 1'
- end
- end
-
- it 'show the message about comments' do
+ expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
expect(page).to have_content 'Not all comments are displayed'
- end
- it 'shows comments that were last relevant at that version' do
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
@@ -141,7 +124,7 @@ feature 'Merge Request versions', :js do
outdated_diff_note.position = outdated_diff_note.original_position
outdated_diff_note.save!
- visit current_url
+ refresh
wait_for_requests
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
@@ -151,7 +134,7 @@ feature 'Merge Request versions', :js do
expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
end
- it 'should return to latest version when "Show latest version" button is clicked' do
+ it 'returns to latest version when "Show latest version" button is clicked' do
click_link 'Show latest version'
page.within '.mr-version-dropdown' do
expect(page).to have_content 'latest version'
@@ -173,7 +156,7 @@ feature 'Merge Request versions', :js do
end
end
- it 'should have 0 chages between versions' do
+ it 'has 0 chages between versions' do
page.within '.mr-version-compare-dropdown' do
expect(find('.dropdown-toggle')).to have_content 'version 1'
end
@@ -194,7 +177,7 @@ feature 'Merge Request versions', :js do
end
end
- it 'should set the compared versions to be the same' do
+ it 'sets the compared versions to be the same' do
page.within '.mr-version-compare-dropdown' do
expect(find('.dropdown-toggle')).to have_content 'version 2'
end
diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
index 2617e735c25..bc25243244e 100644
--- a/spec/features/merge_requests/wip_message_spec.rb
+++ b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
@@ -1,8 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Work In Progress help message' do
- let!(:project) { create(:project, :public, :repository) }
- let!(:user) { create(:user) }
+describe 'Merge request > User sees WIP help message' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
before do
project.add_master(user)
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
index 486555ed5cd..fb73ab05f87 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
@@ -1,13 +1,12 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Create New Merge Request', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User selects branches for new MR', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
before do
project.add_master(user)
-
- sign_in user
+ sign_in(user)
end
it 'selects the source branch sha when a tag with the same name exists' do
diff --git a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index fa3d988b27a..2e95a628013 100644
--- a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -1,10 +1,13 @@
-require 'spec_helper'
+require 'rails_helper'
+
+describe 'Merge request > User toggles whitespace changes', :js do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:user) { project.creator }
-feature 'Toggle Whitespace Changes', :js do
before do
- sign_in(create(:admin))
- merge_request = create(:merge_request)
- project = merge_request.source_project
+ project.add_master(user)
+ sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_request/user_uses_slash_commands_spec.rb
index 5874bf5e187..bd739e69d6c 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_request/user_uses_slash_commands_spec.rb
@@ -1,8 +1,14 @@
require 'rails_helper'
-feature 'Merge Requests > User uses quick actions', :js do
+describe 'Merge request > User uses quick actions', :js do
include QuickActionsHelpers
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:guest) { create(:user) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
+
it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do
let(:issuable) { create(:merge_request, source_project: project) }
let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
@@ -20,15 +26,7 @@ feature 'Merge Requests > User uses quick actions', :js do
visit project_merge_request_path(project, merge_request)
end
- after do
- wait_for_requests
- end
-
describe 'time tracking' do
- before do
- visit project_merge_request_path(project, merge_request)
- end
-
it_behaves_like 'issuable time tracker'
end
@@ -56,7 +54,6 @@ feature 'Merge Requests > User uses quick actions', :js do
end
context 'when the current user cannot toggle the WIP prefix' do
- let(:guest) { create(:user) }
before do
project.add_guest(guest)
sign_out(:user)
@@ -102,7 +99,6 @@ feature 'Merge Requests > User uses quick actions', :js do
end
context 'when the current user cannot merge the MR' do
- let(:guest) { create(:user) }
before do
project.add_guest(guest)
sign_out(:user)
@@ -186,7 +182,6 @@ feature 'Merge Requests > User uses quick actions', :js do
end
context 'when current user can not change target branch' do
- let(:guest) { create(:user) }
before do
project.add_guest(guest)
sign_out(:user)
diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb
deleted file mode 100644
index b2d64a62b4f..00000000000
--- a/spec/features/merge_requests/assign_issues_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'rails_helper'
-
-feature 'Merge request issue assignment', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
- let(:issue1) { create(:issue, project: project) }
- let(:issue2) { create(:issue, project: project) }
- let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue1.to_reference} and #{issue2.to_reference}") }
- let(:service) { MergeRequests::AssignIssuesService.new(merge_request, user, user, project) }
-
- before do
- project.add_developer(user)
- end
-
- def visit_merge_request(current_user = nil)
- sign_in(current_user || user)
- visit project_merge_request_path(project, merge_request)
- end
-
- context 'logged in as author' do
- it 'updates related issues' do
- visit_merge_request
- click_link "Assign yourself to these issues"
-
- expect(page).to have_content "2 issues have been assigned to you"
- end
-
- it 'returns user to the merge request' do
- visit_merge_request
- click_link "Assign yourself to these issues"
-
- expect(page).to have_content merge_request.description
- end
-
- it "doesn't display if related issues are already assigned" do
- [issue1, issue2].each { |issue| issue.update!(assignees: [user]) }
-
- visit_merge_request
-
- expect(page).not_to have_content "Assign yourself"
- end
- end
-
- context 'not MR author' do
- it "doesn't not show assignment link" do
- visit_merge_request(create(:user))
-
- expect(page).not_to have_content "Assign yourself"
- end
- end
-end
diff --git a/spec/features/merge_requests/create_new_mr_from_fork_spec.rb b/spec/features/merge_requests/create_new_mr_from_fork_spec.rb
deleted file mode 100644
index 93c40ff6443..00000000000
--- a/spec/features/merge_requests/create_new_mr_from_fork_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'spec_helper'
-
-feature 'Creating a merge request from a fork', :js do
- include ProjectForksHelper
-
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
- let!(:source_project) do
- fork_project(project, user,
- repository: true,
- namespace: user.namespace)
- end
-
- before do
- source_project.add_master(user)
-
- sign_in(user)
- end
-
- shared_examples 'create merge request to other project' do
- it 'has all possible target projects' do
- visit project_new_merge_request_path(source_project)
-
- first('.js-target-project').click
-
- within('.dropdown-target-project .dropdown-content') do
- expect(page).to have_content(project.full_path)
- expect(page).to have_content(target_project.full_path)
- expect(page).to have_content(source_project.full_path)
- end
- end
-
- it 'allows creating the merge request to another target project' do
- visit project_merge_requests_path(source_project)
-
- page.within '.content' do
- click_link 'New merge request'
- end
-
- find('.js-source-branch', match: :first).click
- find('.dropdown-source-branch .dropdown-content a', match: :first).click
-
- first('.js-target-project').click
- find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click
-
- click_button 'Compare branches and continue'
-
- wait_for_requests
-
- expect { click_button 'Submit merge request' }
- .to change { target_project.merge_requests.reload.size }.by(1)
- end
-
- it 'updates the branches when selecting a new target project' do
- target_project_member = target_project.owner
- CreateBranchService.new(target_project, target_project_member)
- .execute('a-brand-new-branch-to-test', 'master')
- visit project_new_merge_request_path(source_project)
-
- first('.js-target-project').click
- find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click
-
- wait_for_requests
-
- first('.js-target-branch').click
-
- within('.dropdown-target-branch .dropdown-content') do
- expect(page).to have_content('a-brand-new-branch-to-test')
- end
- end
- end
-
- context 'creating to the source of a fork' do
- let!(:target_project) { project }
-
- it_behaves_like('create merge request to other project')
- end
-
- context 'creating to a sibling of a fork' do
- let!(:target_project) do
- other_user = create(:user)
- fork_project(project, other_user,
- repository: true,
- namespace: other_user.namespace)
- end
-
- it_behaves_like('create merge request to other project')
- end
-end
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
deleted file mode 100644
index 53b62caf743..00000000000
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-require 'spec_helper'
-
-feature 'Merge request created from fork' do
- include ProjectForksHelper
-
- given(:user) { create(:user) }
- given(:project) { create(:project, :public, :repository) }
- given(:forked_project) { fork_project(project, user, repository: true) }
-
- given!(:merge_request) do
- create(:merge_request_with_diffs, source_project: forked_project,
- target_project: project,
- description: 'Test merge request')
- end
-
- background do
- forked_project.add_master(user)
- sign_in user
- end
-
- scenario 'user can access merge request' do
- visit_merge_request(merge_request)
-
- expect(page).to have_content 'Test merge request'
- end
-
- context 'when a commit comment exists on the merge request' do
- given(:comment) { 'A commit comment' }
- given(:reply) { 'A reply comment' }
-
- background do
- create(:note_on_commit, note: comment,
- project: forked_project,
- commit_id: merge_request.commit_shas.first)
- end
-
- scenario 'user can reply to the comment', :js do
- visit_merge_request(merge_request)
-
- expect(page).to have_content(comment)
-
- page.within('.discussion-notes') do
- find('.btn-text-field').click
- find('#note_note').send_keys(reply)
- find('.comment-btn').click
- end
-
- wait_for_requests
-
- expect(page).to have_content(reply)
- end
- end
-
- context 'source project is deleted' do
- background do
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- forked_project.destroy!
- end
-
- scenario 'user can access merge request', :js do
- visit_merge_request(merge_request)
-
- expect(page).to have_content 'Test merge request'
- expect(page).to have_content "(removed):#{merge_request.source_branch}"
- end
- end
-
- context 'pipeline present in source project' do
- given(:pipeline) do
- create(:ci_pipeline,
- project: forked_project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch)
- end
-
- background do
- create(:ci_build, pipeline: pipeline, name: 'rspec')
- create(:ci_build, pipeline: pipeline, name: 'spinach')
- end
-
- scenario 'user visits a pipelines page', :js do
- visit_merge_request(merge_request)
- page.within('.merge-request-tabs') { click_link 'Pipelines' }
-
- page.within('.ci-table') do
- expect(page).to have_content pipeline.id
- end
- end
- end
-
- def visit_merge_request(mr)
- visit project_merge_request_path(project, mr)
- end
-end
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
deleted file mode 100644
index 79be2fbf945..00000000000
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-require 'spec_helper'
-
-feature 'Edit Merge Request' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
- let(:merge_request) { create(:merge_request, :simple, source_project: project) }
-
- before do
- project.add_master(user)
-
- sign_in user
-
- visit edit_project_merge_request_path(project, merge_request)
- end
-
- context 'editing a MR' do
- it 'has class js-quick-submit in form' do
- expect(page).to have_selector('.js-quick-submit')
- end
-
- it 'warns about version conflict' do
- merge_request.update(title: "New title")
-
- fill_in 'merge_request_title', with: 'bug 345'
- fill_in 'merge_request_description', with: 'bug description'
-
- click_button 'Save changes'
-
- expect(page).to have_content 'Someone edited the merge request the same time you did'
- end
-
- it 'allows to unselect "Remove source branch"', :js do
- merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
- expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
-
- visit edit_project_merge_request_path(project, merge_request)
- uncheck 'Remove source branch when merge request is accepted'
-
- click_button 'Save changes'
-
- expect(page).to have_unchecked_field 'remove-source-branch-input'
- expect(page).to have_content 'Remove source branch'
- end
-
- it 'should preserve description textarea height', :js do
- long_description = %q(
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat.
-
- Cras congue nec ligula tristique viverra. Curabitur fringilla fringilla fringilla. Donec rhoncus dignissim orci ut accumsan. Ut rutrum urna a rhoncus varius. Maecenas blandit, mauris nec accumsan gravida, augue nibh finibus magna, sed maximus turpis libero nec neque. Suspendisse at semper est. Nunc imperdiet dapibus dui, varius sollicitudin erat luctus non. Sed pellentesque ligula eget posuere facilisis. Donec dictum commodo volutpat. Donec egestas dui ac magna sollicitudin bibendum. Vivamus purus neque, ullamcorper ac feugiat et, tempus sit amet metus. Praesent quis viverra neque. Sed bibendum viverra est, eu aliquam mi ornare vitae. Proin et dapibus ipsum. Nunc tortor diam, malesuada nec interdum vel, placerat quis justo. Ut viverra at erat eu laoreet.
-
- Pellentesque commodo, diam sit amet dignissim condimentum, tortor justo pretium est, non venenatis metus eros ut nunc. Etiam ut neque eget sem dapibus aliquam. Curabitur vel elit lorem. Nulla nec enim elit. Sed ut ex id justo facilisis convallis at ac augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam cursus egestas turpis non tristique. Suspendisse in erat sem. Fusce libero elit, fermentum gravida mauris id, auctor iaculis felis. Nullam vulputate tempor laoreet.
-
- Nam tempor et magna sed convallis. Fusce sit amet sollicitudin risus, a ullamcorper lacus. Morbi gravida quis sem eget porttitor. Donec eu egestas mauris, in elementum tortor. Sed eget ex mi. Mauris iaculis tortor ut est auctor, nec dignissim quam sagittis. Suspendisse vel metus non quam suscipit tincidunt. Cras molestie lacus non justo finibus sodales quis vitae erat. In a porttitor nisi, id sollicitudin urna. Ut at felis tellus. Suspendisse potenti.
-
- Maecenas leo ligula, varius at neque vitae, ornare maximus justo. Nullam convallis luctus risus et vulputate. Duis suscipit faucibus iaculis. Etiam quis tortor faucibus, tristique tellus sit amet, sodales neque. Nulla dapibus nisi vel aliquet consequat. Etiam faucibus, metus eget condimentum iaculis, enim urna lobortis sem, id efficitur eros sapien nec nisi. Aenean ut finibus ex.
- )
-
- fill_in 'merge_request_description', with: long_description
-
- height = get_textarea_height
- find('.js-md-preview-button').click
- find('.js-md-write-button').click
- new_height = get_textarea_height
-
- expect(height).to eq(new_height)
- end
-
- def get_textarea_height
- find('#merge_request_description')
- page.evaluate_script('document.getElementById("merge_request_description").offsetHeight')
- end
- end
-end
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
deleted file mode 100644
index 7adae08e499..00000000000
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'rails_helper'
-
-feature 'Merge Request filtering by Labels', :js do
- include FilteredSearchHelpers
- include MergeRequestHelpers
-
- let(:project) { create(:project, :public, :repository) }
- let!(:user) { create(:user) }
- let!(:label) { create(:label, project: project) }
-
- let!(:bug) { create(:label, project: project, title: 'bug') }
- let!(:feature) { create(:label, project: project, title: 'feature') }
- let!(:enhancement) { create(:label, project: project, title: 'enhancement') }
-
- let!(:mr1) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "fix") }
- let!(:mr2) { create(:merge_request, title: "Bugfix2", source_project: project, target_project: project, source_branch: "wip") }
- let!(:mr3) { create(:merge_request, title: "Feature1", source_project: project, target_project: project, source_branch: "improve/awesome") }
-
- before do
- mr1.labels << bug
-
- mr2.labels << bug
- mr2.labels << enhancement
-
- mr3.title = "Feature1"
- mr3.labels << feature
-
- project.add_master(user)
- sign_in(user)
-
- visit project_merge_requests_path(project)
- end
-
- context 'filter by label bug' do
- before do
- input_filtered_search('label:~bug')
- end
-
- it 'apply the filter' do
- expect(page).to have_content "Bugfix1"
- expect(page).to have_content "Bugfix2"
- expect(page).not_to have_content "Feature1"
- end
- end
-
- context 'filter by label feature' do
- before do
- input_filtered_search('label:~feature')
- end
-
- it 'applies the filter' do
- expect(page).to have_content "Feature1"
- expect(page).not_to have_content "Bugfix2"
- expect(page).not_to have_content "Bugfix1"
- end
- end
-
- context 'filter by label enhancement' do
- before do
- input_filtered_search('label:~enhancement')
- end
-
- it 'applies the filter' do
- expect(page).to have_content "Bugfix2"
- expect(page).not_to have_content "Feature1"
- expect(page).not_to have_content "Bugfix1"
- end
- end
-
- context 'filter by label enhancement and bug in issues list' do
- before do
- input_filtered_search('label:~bug label:~enhancement')
- end
-
- it 'applies the filters' do
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_content "Bugfix2"
- expect(page).not_to have_content "Feature1"
- end
- end
-
- context 'filter dropdown' do
- it 'filters by label name' do
- init_label_search
- filtered_search.send_keys('~bug')
-
- page.within '.filter-dropdown' do
- expect(page).not_to have_content 'enhancement'
- expect(page).to have_content 'bug'
- end
- end
- end
-end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
deleted file mode 100644
index 8db94352f73..00000000000
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'rails_helper'
-
-feature 'Merge Request filtering by Milestone' do
- include FilteredSearchHelpers
- include MergeRequestHelpers
-
- let(:project) { create(:project, :public, :repository) }
- let!(:user) { create(:user)}
- let(:milestone) { create(:milestone, project: project) }
-
- def filter_by_milestone(title)
- find(".js-milestone-select").click
- find(".milestone-filter a", text: title).click
- end
-
- before do
- project.add_master(user)
- sign_in(user)
- end
-
- scenario 'filters by no Milestone', :js do
- create(:merge_request, :with_diffs, source_project: project)
- create(:merge_request, :simple, source_project: project, milestone: milestone)
-
- visit_merge_requests(project)
- input_filtered_search('milestone:none')
-
- expect_tokens([milestone_token('none', false)])
- expect_filtered_search_input_empty
-
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_css('.merge-request', count: 1)
- end
-
- context 'filters by upcoming milestone', :js do
- it 'does not show merge requests with no expiry' do
- create(:merge_request, :with_diffs, source_project: project)
- create(:merge_request, :simple, source_project: project, milestone: milestone)
-
- visit_merge_requests(project)
- input_filtered_search('milestone:upcoming')
-
- expect(page).to have_css('.merge-request', count: 0)
- end
-
- it 'shows merge requests in future' do
- milestone = create(:milestone, project: project, due_date: Date.tomorrow)
- create(:merge_request, :with_diffs, source_project: project)
- create(:merge_request, :simple, source_project: project, milestone: milestone)
-
- visit_merge_requests(project)
- input_filtered_search('milestone:upcoming')
-
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_css('.merge-request', count: 1)
- end
-
- it 'does not show merge requests in past' do
- milestone = create(:milestone, project: project, due_date: Date.yesterday)
- create(:merge_request, :with_diffs, source_project: project)
- create(:merge_request, :simple, source_project: project, milestone: milestone)
-
- visit_merge_requests(project)
- input_filtered_search('milestone:upcoming')
-
- expect(page).to have_css('.merge-request', count: 0)
- end
- end
-
- scenario 'filters by a specific Milestone', :js do
- create(:merge_request, :with_diffs, source_project: project, milestone: milestone)
- create(:merge_request, :simple, source_project: project)
-
- visit_merge_requests(project)
- input_filtered_search("milestone:%'#{milestone.title}'")
-
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_css('.merge-request', count: 1)
- end
-
- context 'when milestone has single quotes in title' do
- background do
- milestone.update(name: "rock 'n' roll")
- end
-
- scenario 'filters by a specific Milestone', :js do
- create(:merge_request, :with_diffs, source_project: project, milestone: milestone)
- create(:merge_request, :simple, source_project: project)
-
- visit_merge_requests(project)
- input_filtered_search("milestone:%\"#{milestone.title}\"")
-
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_css('.merge-request', count: 1)
- end
- end
-end
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
deleted file mode 100644
index aac295ab940..00000000000
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ /dev/null
@@ -1,337 +0,0 @@
-require 'rails_helper'
-
-describe 'Filter merge requests' do
- include FilteredSearchHelpers
- include MergeRequestHelpers
-
- let!(:project) { create(:project, :repository) }
- let!(:group) { create(:group) }
- let!(:user) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:label) { create(:label, project: project) }
- let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
-
- before do
- project.add_master(user)
- group.add_developer(user)
- sign_in(user)
- create(:merge_request, source_project: project, target_project: project)
-
- visit project_merge_requests_path(project)
- end
-
- describe 'for assignee from mr#index' do
- let(:search_query) { "assignee:@#{user.username}" }
-
- def expect_assignee_visual_tokens
- wait_for_requests
-
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- before do
- input_filtered_search(search_query)
-
- expect_mr_list_count(0)
- end
-
- context 'assignee', :js do
- it 'updates to current user' do
- expect_assignee_visual_tokens()
- end
-
- it 'does not change when closed link is clicked' do
- find('.issues-state-filters [data-state="closed"]').click
-
- expect_assignee_visual_tokens()
- end
-
- it 'does not change when all link is clicked' do
- find('.issues-state-filters [data-state="all"]').click
-
- expect_assignee_visual_tokens()
- end
- end
- end
-
- describe 'for milestone from mr#index' do
- let(:search_query) { "milestone:%\"#{milestone.title}\"" }
-
- def expect_milestone_visual_tokens
- expect_tokens([milestone_token("\"#{milestone.title}\"")])
- expect_filtered_search_input_empty
- end
-
- before do
- input_filtered_search(search_query)
-
- expect_mr_list_count(0)
- end
-
- context 'milestone', :js do
- it 'updates to current milestone' do
- expect_milestone_visual_tokens()
- end
-
- it 'does not change when closed link is clicked' do
- find('.issues-state-filters [data-state="closed"]').click
-
- expect_milestone_visual_tokens()
- end
-
- it 'does not change when all link is clicked' do
- find('.issues-state-filters [data-state="all"]').click
-
- expect_milestone_visual_tokens()
- end
- end
- end
-
- describe 'for label from mr#index', :js do
- it 'filters by no label' do
- input_filtered_search('label:none')
-
- expect_mr_list_count(1)
- expect_tokens([label_token('none', false)])
- expect_filtered_search_input_empty
- end
-
- it 'filters by a label' do
- input_filtered_search("label:~#{label.title}")
-
- expect_mr_list_count(0)
- expect_tokens([label_token(label.title)])
- expect_filtered_search_input_empty
- end
-
- it "filters by `won't fix` and another label" do
- input_filtered_search("label:~\"#{wontfix.title}\" label:~#{label.title}")
-
- expect_mr_list_count(0)
- expect_tokens([label_token("\"#{wontfix.title}\""), label_token(label.title)])
- expect_filtered_search_input_empty
- end
-
- it "filters by `won't fix` label followed by another label after page load" do
- input_filtered_search("label:~\"#{wontfix.title}\"")
-
- expect_mr_list_count(0)
- expect_tokens([label_token("\"#{wontfix.title}\"")])
- expect_filtered_search_input_empty
-
- input_filtered_search_keys("label:~#{label.title}")
-
- expect_mr_list_count(0)
- expect_tokens([label_token("\"#{wontfix.title}\""), label_token(label.title)])
- expect_filtered_search_input_empty
- end
- end
-
- describe 'for assignee and label from mr#index' do
- let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" }
-
- before do
- input_filtered_search(search_query)
-
- expect_mr_list_count(0)
- end
-
- context 'assignee and label', :js do
- def expect_assignee_label_visual_tokens
- wait_for_requests
-
- expect_tokens([assignee_token(user.name), label_token(label.title)])
- expect_filtered_search_input_empty
- end
-
- it 'updates to current assignee and label' do
- expect_assignee_label_visual_tokens()
- end
-
- it 'does not change when closed link is clicked' do
- find('.issues-state-filters [data-state="closed"]').click
-
- expect_assignee_label_visual_tokens()
- end
-
- it 'does not change when all link is clicked' do
- find('.issues-state-filters [data-state="all"]').click
-
- expect_assignee_label_visual_tokens()
- end
- end
- end
-
- describe 'filter merge requests by text' do
- before do
- create(:merge_request, title: "Bug", source_project: project, target_project: project, source_branch: "wip")
-
- bug_label = create(:label, project: project, title: 'bug')
- milestone = create(:milestone, title: "8", project: project)
-
- mr = create(:merge_request,
- title: "Bug 2",
- source_project: project,
- target_project: project,
- source_branch: "fix",
- milestone: milestone,
- author: user,
- assignee: user)
- mr.labels << bug_label
-
- visit project_merge_requests_path(project)
- end
-
- context 'only text', :js do
- it 'filters merge requests by searched text' do
- input_filtered_search('bug')
-
- expect_mr_list_count(2)
- end
-
- it 'does not show any merge requests' do
- input_filtered_search('testing')
-
- page.within '.mr-list' do
- expect(page).not_to have_selector('.merge-request')
- end
- end
- end
-
- context 'filters and searches', :js do
- it 'filters by text and label' do
- input_filtered_search('Bug')
-
- expect_mr_list_count(2)
- expect_filtered_search_input('Bug')
-
- input_filtered_search_keys(' label:~bug')
-
- expect_mr_list_count(1)
- expect_tokens([label_token('bug')])
- expect_filtered_search_input('Bug')
- end
-
- it 'filters by text and milestone' do
- input_filtered_search('Bug')
-
- expect_mr_list_count(2)
- expect_filtered_search_input('Bug')
-
- input_filtered_search_keys(' milestone:%8')
-
- expect_mr_list_count(1)
- expect_tokens([milestone_token('8')])
- expect_filtered_search_input('Bug')
- end
-
- it 'filters by text and assignee' do
- input_filtered_search('Bug')
-
- expect_mr_list_count(2)
- expect_filtered_search_input('Bug')
-
- input_filtered_search_keys(" assignee:@#{user.username}")
-
- expect_mr_list_count(1)
-
- wait_for_requests
-
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input('Bug')
- end
-
- it 'filters by text and author' do
- input_filtered_search('Bug')
-
- expect_mr_list_count(2)
- expect_filtered_search_input('Bug')
-
- input_filtered_search_keys(" author:@#{user.username}")
-
- wait_for_requests
-
- expect_mr_list_count(1)
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input('Bug')
- end
- end
- end
-
- describe 'filter merge requests and sort', :js do
- before do
- bug_label = create(:label, project: project, title: 'bug')
-
- mr1 = create(:merge_request, title: "Frontend", source_project: project, target_project: project, source_branch: "wip")
- mr2 = create(:merge_request, title: "Bug 2", source_project: project, target_project: project, source_branch: "fix")
-
- mr1.labels << bug_label
- mr2.labels << bug_label
-
- visit project_merge_requests_path(project)
- end
-
- it 'is able to filter and sort merge requests' do
- input_filtered_search('label:~bug')
-
- expect_mr_list_count(2)
-
- click_button 'Created date'
- page.within '.dropdown-menu-sort' do
- click_link 'Priority'
- end
- wait_for_requests
-
- page.within '.mr-list' do
- expect(page).to have_content('Frontend')
- end
- end
- end
-
- describe 'filter by assignee id', :js do
- it 'filter by current user' do
- visit project_merge_requests_path(project, assignee_id: user.id)
-
- wait_for_requests
-
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'filter by new user' do
- new_user = create(:user)
- project.add_developer(new_user)
-
- visit project_merge_requests_path(project, assignee_id: new_user.id)
-
- wait_for_requests
-
- expect_tokens([assignee_token(new_user.name)])
- expect_filtered_search_input_empty
- end
- end
-
- describe 'filter by author id', :js do
- it 'filter by current user' do
- visit project_merge_requests_path(project, author_id: user.id)
-
- wait_for_requests
-
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'filter by new user' do
- new_user = create(:user)
- project.add_developer(new_user)
-
- visit project_merge_requests_path(project, author_id: new_user.id)
-
- wait_for_requests
-
- expect_tokens([author_token(new_user.name)])
- expect_filtered_search_input_empty
- end
- end
-end
diff --git a/spec/features/merge_requests/filters_generic_behavior_spec.rb b/spec/features/merge_requests/filters_generic_behavior_spec.rb
new file mode 100644
index 00000000000..0e7fac6b409
--- /dev/null
+++ b/spec/features/merge_requests/filters_generic_behavior_spec.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+describe 'Merge Requests > Filters generic behavior', :js do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:bug) { create(:label, project: project, title: 'bug') }
+ let(:open_mr) { create(:merge_request, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1') }
+ let(:merged_mr) { create(:merge_request, :merged, title: 'Bugfix2', source_project: project, target_project: project, source_branch: 'bugfix2') }
+ let(:closed_mr) { create(:merge_request, :closed, title: 'Feature', source_project: project, target_project: project, source_branch: 'improve/awesome') }
+
+ before do
+ open_mr.labels << bug
+ merged_mr.labels << bug
+ closed_mr.labels << bug
+
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ context 'when filtered by a label' do
+ before do
+ input_filtered_search('label:~bug')
+ end
+
+ describe 'state tabs' do
+ it 'does not change when state tabs are clicked' do
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ expect(page).not_to have_content 'Feature'
+
+ find('.issues-state-filters [data-state="merged"]').click
+
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).not_to have_content 'Bugfix1'
+ expect(page).to have_content 'Bugfix2'
+ expect(page).not_to have_content 'Feature'
+
+ find('.issues-state-filters [data-state="closed"]').click
+
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).not_to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ expect(page).to have_content 'Feature'
+
+ find('.issues-state-filters [data-state="all"]').click
+
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).to have_content 'Bugfix1'
+ expect(page).to have_content 'Bugfix2'
+ expect(page).to have_content 'Feature'
+ end
+ end
+
+ describe 'clear button' do
+ it 'allows user to remove filtered labels' do
+ first('.clear-search').click
+ filtered_search.send_keys(:enter)
+
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ expect(page).not_to have_content 'Feature'
+ end
+ end
+ end
+
+ context 'filter dropdown' do
+ it 'filters by label name' do
+ init_label_search
+ filtered_search.send_keys('~bug')
+
+ page.within '.filter-dropdown' do
+ expect(page).not_to have_content 'enhancement'
+ expect(page).to have_content 'bug'
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
deleted file mode 100644
index 1ebf762a006..00000000000
--- a/spec/features/merge_requests/form_spec.rb
+++ /dev/null
@@ -1,301 +0,0 @@
-require 'rails_helper'
-
-describe 'New/edit merge request', :js do
- include ProjectForksHelper
-
- let!(:project) { create(:project, :public, :repository) }
- let(:forked_project) { fork_project(project, nil, repository: true) }
- let!(:user) { create(:user) }
- let!(:user2) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:label) { create(:label, project: project) }
- let!(:label2) { create(:label, project: project) }
-
- before do
- project.add_master(user)
- project.add_master(user2)
- end
-
- context 'owned projects' do
- before do
- sign_in(user)
- end
-
- context 'new merge request' do
- before do
- visit project_new_merge_request_path(
- project,
- merge_request: {
- source_project_id: project.id,
- target_project_id: project.id,
- source_branch: 'fix',
- target_branch: 'master'
- })
- end
-
- it 'creates new merge request' do
- click_button 'Assignee'
- page.within '.dropdown-menu-user' do
- click_link user2.name
- end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user2.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user2.name
- end
-
- find('a', text: 'Assign to me').click
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
- end
-
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
- expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
-
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
- end
- page.within '.js-label-select' do
- expect(page).to have_content label.title
- end
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
-
- click_button 'Submit merge request'
-
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content user.name
- end
-
- page.within '.milestone' do
- expect(page).to have_content milestone.title
- end
-
- page.within '.labels' do
- expect(page).to have_content label.title
- expect(page).to have_content label2.title
- end
- end
-
- page.within '.breadcrumbs' do
- merge_request = MergeRequest.find_by(source_branch: 'fix')
-
- expect(page).to have_text("Merge Requests #{merge_request.to_reference}")
- end
- end
-
- it 'description has autocomplete' do
- find('#merge_request_description').native.send_keys('')
- fill_in 'merge_request_description', with: '@'
-
- expect(page).to have_selector('.atwho-view')
- end
- end
-
- context 'edit merge request' do
- before do
- merge_request = create(:merge_request,
- source_project: project,
- target_project: project,
- source_branch: 'fix',
- target_branch: 'master'
- )
-
- visit edit_project_merge_request_path(project, merge_request)
- end
-
- it 'updates merge request' do
- click_button 'Assignee'
- page.within '.dropdown-menu-user' do
- click_link user.name
- end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
- end
-
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
- expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
-
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
- end
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
- page.within '.js-label-select' do
- expect(page).to have_content label.title
- end
-
- click_button 'Save changes'
-
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content user.name
- end
-
- page.within '.milestone' do
- expect(page).to have_content milestone.title
- end
-
- page.within '.labels' do
- expect(page).to have_content label.title
- expect(page).to have_content label2.title
- end
- end
- end
-
- it 'description has autocomplete' do
- find('#merge_request_description').native.send_keys('')
- fill_in 'merge_request_description', with: '@'
-
- expect(page).to have_selector('.atwho-view')
- end
- end
- end
-
- context 'forked project' do
- before do
- forked_project.add_master(user)
- sign_in(user)
- end
-
- context 'new merge request' do
- before do
- visit project_new_merge_request_path(
- forked_project,
- merge_request: {
- source_project_id: forked_project.id,
- target_project_id: project.id,
- source_branch: 'fix',
- target_branch: 'master'
- })
- end
-
- it 'creates new merge request' do
- click_button 'Assignee'
- page.within '.dropdown-menu-user' do
- click_link user.name
- end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
- end
-
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
- expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
-
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
- end
- page.within '.js-label-select' do
- expect(page).to have_content label.title
- end
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
-
- click_button 'Submit merge request'
-
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content user.name
- end
-
- page.within '.milestone' do
- expect(page).to have_content milestone.title
- end
-
- page.within '.labels' do
- expect(page).to have_content label.title
- expect(page).to have_content label2.title
- end
- end
- end
- end
-
- context 'edit merge request' do
- before do
- merge_request = create(:merge_request,
- source_project: forked_project,
- target_project: project,
- source_branch: 'fix',
- target_branch: 'master'
- )
-
- visit edit_project_merge_request_path(project, merge_request)
- end
-
- it 'should update merge request' do
- click_button 'Assignee'
- page.within '.dropdown-menu-user' do
- click_link user.name
- end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
- end
-
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
- expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
-
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
- end
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
- page.within '.js-label-select' do
- expect(page).to have_content label.title
- end
-
- click_button 'Save changes'
-
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content user.name
- end
-
- page.within '.milestone' do
- expect(page).to have_content milestone.title
- end
-
- page.within '.labels' do
- expect(page).to have_content label.title
- expect(page).to have_content label2.title
- end
- end
- end
- end
- end
-end
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
deleted file mode 100644
index daca4422bf1..00000000000
--- a/spec/features/merge_requests/reset_filters_spec.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-require 'rails_helper'
-
-feature 'Merge requests filter clear button', :js do
- include FilteredSearchHelpers
- include MergeRequestHelpers
- include IssueHelpers
-
- let!(:project) { create(:project, :public, :repository) }
- let!(:user) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:bug) { create(:label, project: project, name: 'bug')}
- let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "improve/awesome", milestone: milestone, author: user, assignee: user) }
- let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "fix") }
-
- let(:merge_request_css) { '.merge-request' }
- let(:clear_search_css) { '.filtered-search-box .clear-search' }
-
- before do
- mr2.labels << bug
- project.add_developer(user)
- end
-
- context 'when a milestone filter has been applied' do
- it 'resets the milestone filter' do
- visit_merge_requests(project, milestone_title: milestone.title)
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when a label filter has been applied' do
- it 'resets the label filter' do
- visit_merge_requests(project, label_name: bug.name)
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when multiple label filters have been applied' do
- let!(:label) { create(:label, project: project, name: 'Frontend') }
- let(:filter_dropdown) { find("#js-dropdown-label .filter-dropdown") }
-
- before do
- visit_merge_requests(project)
- init_label_search
- end
-
- it 'filters bug label' do
- filtered_search.set('~bug')
-
- filter_dropdown.find('.filter-dropdown-item', text: bug.title).click
- init_label_search
-
- expect(filter_dropdown.find('.filter-dropdown-item', text: bug.title)).to be_visible
- expect(filter_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
- end
- end
-
- context 'when a text search has been conducted' do
- it 'resets the text search filter' do
- visit_merge_requests(project, search: 'Bug')
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when author filter has been applied' do
- it 'resets the author filter' do
- visit_merge_requests(project, author_username: user.username)
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when assignee filter has been applied' do
- it 'resets the assignee filter' do
- visit_merge_requests(project, assignee_username: user.username)
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when all filters have been applied' do
- it 'clears all filters' do
- visit_merge_requests(project, assignee_username: user.username, author_username: user.username, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
-
- expect(page).to have_css(merge_request_css, count: 0)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when no filters have been applied' do
- it 'the clear button should not be visible' do
- visit_merge_requests(project)
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- expect(page).not_to have_css(clear_search_css)
- end
- end
-end
diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb
deleted file mode 100644
index d9f7a056dea..00000000000
--- a/spec/features/merge_requests/target_branch_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe 'Target branch', :js do
- let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request) }
- let(:project) { merge_request.project }
-
- def path_to_merge_request
- project_merge_request_path(project, merge_request)
- end
-
- before do
- sign_in user
- project.add_master(user)
- end
-
- context 'when branch was deleted' do
- before do
- DeleteBranchService.new(project, user).execute('feature')
- visit path_to_merge_request
- end
-
- it 'shows a message about missing target branch' do
- expect(page).to have_content(
- 'Target branch does not exist'
- )
- end
-
- it 'does not show link to target branch' do
- expect(page).not_to have_selector('.mr-widget-body .js-branch-text a')
- end
- end
-end
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
deleted file mode 100644
index cd92ad22267..00000000000
--- a/spec/features/merge_requests/toggler_behavior_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-feature 'toggler_behavior', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:merge_request) { create(:merge_request, source_project: project, author: user) }
- let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
- let(:fragment_id) { "#note_#{note.id}" }
-
- before do
- sign_in(create(:admin))
- project = merge_request.source_project
- page.current_window.resize_to(1000, 300)
- visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
- end
-
- describe 'scroll position' do
- it 'should be scrolled down to fragment' do
- page_height = page.current_window.size[1]
- page_scroll_y = page.evaluate_script("window.scrollY")
- fragment_position_top = page.evaluate_script("Math.round($('#{fragment_id}').offset().top)")
- expect(find('.js-toggle-content').visible?).to eq true
- expect(find(fragment_id).visible?).to eq true
- expect(fragment_position_top).to be >= page_scroll_y
- expect(fragment_position_top).to be < (page_scroll_y + page_height)
- end
- end
-end
diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
new file mode 100644
index 00000000000..d6c770c93f1
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+describe 'Merge Requests > User filters by assignees', :js do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+
+ before do
+ create(:merge_request, assignee: user, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1')
+ create(:merge_request, title: 'Bugfix2', source_project: project, target_project: project, source_branch: 'bugfix2')
+
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ context 'filtering by assignee:none' do
+ it 'applies the filter' do
+ input_filtered_search('assignee:none')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).not_to have_content 'Bugfix1'
+ expect(page).to have_content 'Bugfix2'
+ end
+ end
+
+ context 'filtering by assignee:@username' do
+ it 'applies the filter' do
+ input_filtered_search("assignee:@#{user.username}")
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_filters_by_labels_spec.rb b/spec/features/merge_requests/user_filters_by_labels_spec.rb
new file mode 100644
index 00000000000..08d741af93d
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_labels_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+describe 'Merge Requests > User filters by labels', :js do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:mr1) { create(:merge_request, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1') }
+ let(:mr2) { create(:merge_request, title: 'Bugfix2', source_project: project, target_project: project, source_branch: 'bugfix2') }
+
+ before do
+ bug_label = create(:label, project: project, title: 'bug')
+ enhancement_label = create(:label, project: project, title: 'enhancement')
+ mr1.labels << bug_label
+ mr2.labels << bug_label << enhancement_label
+
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ context 'filtering by label:none' do
+ it 'applies the filter' do
+ input_filtered_search('label:none')
+
+ expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
+ expect(page).not_to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ end
+ end
+
+ context 'filtering by label:~enhancement' do
+ it 'applies the filter' do
+ input_filtered_search('label:~enhancement')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix2'
+ expect(page).not_to have_content 'Bugfix1'
+ end
+ end
+
+ context 'filtering by label:~enhancement and label:~bug' do
+ it 'applies the filters' do
+ input_filtered_search('label:~bug label:~enhancement')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix2'
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_filters_by_milestones_spec.rb b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
new file mode 100644
index 00000000000..727a236d980
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
@@ -0,0 +1,62 @@
+require 'rails_helper'
+
+describe 'Merge Requests > User filters by milestones', :js do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:milestone) { create(:milestone, project: project) }
+
+ before do
+ create(:merge_request, :with_diffs, source_project: project)
+ create(:merge_request, :simple, source_project: project, milestone: milestone)
+
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ it 'filters by no milestone' do
+ input_filtered_search('milestone:none')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_css('.merge-request', count: 1)
+ end
+
+ it 'filters by a specific milestone' do
+ input_filtered_search("milestone:%'#{milestone.title}'")
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_css('.merge-request', count: 1)
+ end
+
+ describe 'filters by upcoming milestone' do
+ it 'does not show merge requests with no expiry' do
+ input_filtered_search('milestone:upcoming')
+
+ expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
+ expect(page).to have_css('.merge-request', count: 0)
+ end
+
+ context 'with an upcoming milestone' do
+ let(:milestone) { create(:milestone, project: project, due_date: Date.tomorrow) }
+
+ it 'shows merge requests' do
+ input_filtered_search('milestone:upcoming')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_css('.merge-request', count: 1)
+ end
+ end
+
+ context 'with a due milestone' do
+ let(:milestone) { create(:milestone, project: project, due_date: Date.yesterday) }
+
+ it 'does not show any merge requests' do
+ input_filtered_search('milestone:upcoming')
+
+ expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
+ expect(page).to have_css('.merge-request', count: 0)
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
new file mode 100644
index 00000000000..1615899a047
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+describe 'Merge requests > User filters by multiple criteria', :js do
+ include FilteredSearchHelpers
+
+ let!(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let!(:milestone) { create(:milestone, title: 'v1.1', project: project) }
+ let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
+
+ before do
+ sign_in(user)
+ mr = create(:merge_request, title: 'Bugfix2', author: user, assignee: user, source_project: project, target_project: project, milestone: milestone)
+ mr.labels << wontfix
+
+ visit project_merge_requests_path(project)
+ end
+
+ describe 'filtering by label:~"Won\'t fix" and assignee:~bug' do
+ it 'applies the filters' do
+ input_filtered_search("label:~\"Won't fix\" assignee:@#{user.username}")
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix2'
+ expect_filtered_search_input_empty
+ end
+ end
+
+ describe 'filtering by text, author, assignee, milestone, and label' do
+ it 'filters by text, author, assignee, milestone, and label' do
+ input_filtered_search_keys("author:@#{user.username} assignee:@#{user.username} milestone:%\"v1.1\" label:~\"Won't fix\" Bug")
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix2'
+ expect_filtered_search_input('Bug')
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 416a0f78a45..ef7ae490b0f 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -1,6 +1,6 @@
-require 'spec_helper'
+require 'rails_helper'
-describe 'Projects > Merge requests > User lists merge requests' do
+describe 'Merge requests > User lists merge requests' do
include MergeRequestHelpers
include SortingHelper
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index a96404b86ed..199ba7e87ad 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -1,8 +1,8 @@
require 'rails_helper'
-feature 'Multiple merge requests updating from merge_requests#index' do
- let!(:user) { create(:user)}
- let!(:project) { create(:project, :repository) }
+describe 'Merge requests > User mass updates', :js do
+ let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
before do
@@ -10,7 +10,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
sign_in(user)
end
- context 'status', :js do
+ context 'status' do
describe 'close merge request' do
before do
visit project_merge_requests_path(project)
@@ -37,13 +37,13 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
end
- context 'assignee', :js do
+ context 'assignee' do
describe 'set assignee' do
before do
visit project_merge_requests_path(project)
end
- it "updates merge request with assignee" do
+ it 'updates merge request with assignee' do
change_assignee(user.name)
page.within('.merge-request .controls') do
@@ -59,7 +59,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
visit project_merge_requests_path(project)
end
- it "removes assignee from the merge request" do
+ it 'removes assignee from the merge request' do
change_assignee('Unassigned')
expect(find('.merge-request .controls')).not_to have_css('.author_link')
@@ -67,7 +67,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
end
- context 'milestone', :js do
+ context 'milestone' do
let(:milestone) { create(:milestone, project: project) }
describe 'set milestone' do
@@ -75,7 +75,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
visit project_merge_requests_path(project)
end
- it "updates merge request with milestone" do
+ it 'updates merge request with milestone' do
change_milestone(milestone.title)
expect(find('.merge-request')).to have_content milestone.title
@@ -89,7 +89,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
visit project_merge_requests_path(project)
end
- it "removes milestone from the merge request" do
+ it 'removes milestone from the merge request' do
change_milestone("No Milestone")
expect(find('.merge-request')).not_to have_content milestone.title
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
deleted file mode 100644
index ec2da72ddff..00000000000
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'spec_helper'
-
-feature 'Widget Deployments Header', :js do
- describe 'when deployed to an environment' do
- given(:user) { create(:user) }
- given(:project) { merge_request.target_project }
- given(:merge_request) { create(:merge_request, :merged) }
- given(:environment) { create(:environment, project: project) }
- given(:role) { :developer }
- given(:sha) { project.commit('master').id }
- given!(:deployment) { create(:deployment, environment: environment, sha: sha) }
- given!(:manual) { }
-
- background do
- sign_in(user)
- project.add_role(user, role)
- visit project_merge_request_path(project, merge_request)
- end
-
- scenario 'displays that the environment is deployed' do
- wait_for_requests
-
- expect(page).to have_content("Deployed to #{environment.name}")
- expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
- end
-
- context 'with stop action' do
- given(:pipeline) { create(:ci_pipeline, project: project) }
- given(:build) { create(:ci_build, pipeline: pipeline) }
- given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
- given(:deployment) do
- create(:deployment, environment: environment, ref: merge_request.target_branch,
- sha: sha, deployable: build, on_stop: 'close_app')
- end
-
- background do
- wait_for_requests
- end
-
- scenario 'does show stop button' do
- expect(page).to have_button('Stop environment')
- end
-
- scenario 'does start build when stop button clicked' do
- accept_confirm { click_button('Stop environment') }
-
- expect(page).to have_content('close_app')
- end
-
- context 'for reporter' do
- given(:role) { :reporter }
-
- scenario 'does not show stop button' do
- expect(page).not_to have_button('Stop environment')
- end
- end
- end
- end
-end
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index 49d8e52f861..a5e325ee2e3 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -10,8 +10,7 @@ feature 'OAuth Login', :js, :allow_forgery_protection do
def stub_omniauth_config(provider)
OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
- set_devise_mapping(context: Rails.application)
- Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
+ stub_omniauth_provider(provider)
end
providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 266af8f4e3d..90d6841af0e 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -32,18 +32,6 @@ describe 'User visits the profile preferences page' do
end
end
- describe 'User changes their multi file editor preferences', :js do
- it 'set the new_repo cookie when the option is ON' do
- choose 'user_multi_file_on'
- expect(get_cookie('new_repo')).not_to be_nil
- end
-
- it 'deletes the new_repo cookie when the option is OFF' do
- choose 'user_multi_file_off'
- expect(get_cookie('new_repo')).to be_nil
- end
- end
-
describe 'User changes their default dashboard', :js do
it 'creates a flash message' do
select 'Starred Projects', from: 'user_dashboard'
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index af125e1b9d3..e8bb9c6a86c 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -32,7 +32,7 @@ feature 'Import/Export - project import integration test', :js do
expect(page).to have_content('Import an exported GitLab project')
expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}")
- expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\h*\z/).and_call_original
+ expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}\z/).and_call_original
attach_file('file', file)
click_on 'Import project'
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 9a6b27c00f8..a5cd858b11a 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -380,9 +380,18 @@ feature 'Jobs' do
it 'shows manual action empty state' do
expect(page).to have_content('This job requires a manual action')
- expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.')
+ expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
expect(page).to have_link('Trigger this manual action')
end
+
+ it 'plays manual action', :js do
+ click_link 'Trigger this manual action'
+
+ wait_for_requests
+ expect(page).to have_content('This job has not been triggered')
+ expect(page).to have_content('This job is stuck, because the project doesn\'t have any runners online assigned to it.')
+ expect(page).to have_content('pending')
+ end
end
context 'Non triggered job' do
@@ -392,9 +401,8 @@ feature 'Jobs' do
visit project_job_path(project, job)
end
- it 'shows manual action empty state' do
+ it 'shows empty state' do
expect(page).to have_content('This job has not been triggered yet')
- expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered.')
end
end
end
diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb
deleted file mode 100644
index b34b13db381..00000000000
--- a/spec/features/projects/merge_requests/list_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'spec_helper'
-
-feature 'Merge Requests List' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
-
- background do
- project.add_developer(user)
-
- sign_in(user)
- end
-
- scenario 'user does not see create new list button' do
- create(:merge_request, source_project: project)
-
- visit project_merge_requests_path(project)
-
- expect(page).not_to have_selector('.js-new-board-list')
- end
-
- it 'should show an empty state' do
- visit project_merge_requests_path(project)
-
- expect(page).to have_selector('.empty-state')
- end
-
- it 'empty state should have a create merge request button' do
- visit project_merge_requests_path(project)
-
- expect(page).to have_link 'New merge request', href: project_new_merge_request_path(project)
- end
-
- context 'if there are merge requests' do
- before do
- create(:merge_request, assignee: user, source_project: project)
-
- visit project_merge_requests_path(project)
- end
-
- it 'should not show an empty state' do
- expect(page).not_to have_selector('.empty-state')
- end
- end
-end
diff --git a/spec/features/projects/merge_requests/user_manages_subscription_spec.rb b/spec/features/projects/merge_requests/user_manages_subscription_spec.rb
index 4ca435491cb..f55eb5c6664 100644
--- a/spec/features/projects/merge_requests/user_manages_subscription_spec.rb
+++ b/spec/features/projects/merge_requests/user_manages_subscription_spec.rb
@@ -13,20 +13,18 @@ describe 'User manages subscription', :js do
end
it 'toggles subscription' do
- subscribe_button = find('.js-issuable-subscribe-button')
+ page.within('.js-issuable-subscribe-button') do
+ expect(page).to have_css 'button:not(.is-checked)'
+ find('button:not(.is-checked)').click
- expect(subscribe_button).to have_content('Subscribe')
+ wait_for_requests
- click_on('Subscribe')
+ expect(page).to have_css 'button.is-checked'
+ find('button.is-checked').click
- wait_for_requests
+ wait_for_requests
- expect(subscribe_button).to have_content('Unsubscribe')
-
- click_on('Unsubscribe')
-
- wait_for_requests
-
- expect(subscribe_button).to have_content('Subscribe')
+ expect(page).to have_css 'button:not(.is-checked)'
+ end
end
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 81b282502fc..14670e91006 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -43,7 +43,7 @@ feature 'Repository settings' do
fill_in 'deploy_key_title', with: 'new_deploy_key'
fill_in 'deploy_key_key', with: new_ssh_key
- check 'deploy_key_can_push'
+ check 'deploy_key_deploy_keys_projects_attributes_0_can_push'
click_button 'Add key'
expect(page).to have_content('new_deploy_key')
@@ -57,7 +57,7 @@ feature 'Repository settings' do
find('li', text: private_deploy_key.title).click_link('Edit')
fill_in 'deploy_key_title', with: 'updated_deploy_key'
- check 'deploy_key_can_push'
+ check 'deploy_key_deploy_keys_projects_attributes_0_can_push'
click_button 'Save changes'
expect(page).to have_content('updated_deploy_key')
@@ -74,11 +74,9 @@ feature 'Repository settings' do
find('li', text: private_deploy_key.title).click_link('Edit')
fill_in 'deploy_key_title', with: 'updated_deploy_key'
- check 'deploy_key_can_push'
click_button 'Save changes'
expect(page).to have_content('updated_deploy_key')
- expect(page).to have_content('Write access allowed')
end
scenario 'remove an existing deploy key' do
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index 670e8dda916..975c157bcf5 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
describe 'User can display performance bar', :js do
- shared_examples 'performance bar is disabled' do
+ shared_examples 'performance bar cannot be displayed' do
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
end
@@ -17,7 +17,7 @@ describe 'User can display performance bar', :js do
end
end
- shared_examples 'performance bar is enabled' do
+ shared_examples 'performance bar can be displayed' do
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
end
@@ -33,6 +33,18 @@ describe 'User can display performance bar', :js do
end
end
+ shared_examples 'performance bar is enabled by default in development' do
+ before do
+ allow(Rails.env).to receive(:development?).and_return(true)
+ end
+
+ it 'shows the performance bar by default' do
+ refresh # Because we're stubbing Rails.env after the 1st visit to root_path
+
+ expect(page).to have_css('#peek')
+ end
+ end
+
let(:group) { create(:group) }
context 'when user is logged-out' do
@@ -45,7 +57,7 @@ describe 'User can display performance bar', :js do
stub_application_setting(performance_bar_allowed_group_id: nil)
end
- it_behaves_like 'performance bar is disabled'
+ it_behaves_like 'performance bar cannot be displayed'
end
context 'when the performance_bar feature is enabled' do
@@ -53,7 +65,7 @@ describe 'User can display performance bar', :js do
stub_application_setting(performance_bar_allowed_group_id: group.id)
end
- it_behaves_like 'performance bar is disabled'
+ it_behaves_like 'performance bar cannot be displayed'
end
end
@@ -72,7 +84,8 @@ describe 'User can display performance bar', :js do
stub_application_setting(performance_bar_allowed_group_id: nil)
end
- it_behaves_like 'performance bar is disabled'
+ it_behaves_like 'performance bar cannot be displayed'
+ it_behaves_like 'performance bar is enabled by default in development'
end
context 'when the performance_bar feature is enabled' do
@@ -80,7 +93,8 @@ describe 'User can display performance bar', :js do
stub_application_setting(performance_bar_allowed_group_id: group.id)
end
- it_behaves_like 'performance bar is enabled'
+ it_behaves_like 'performance bar is enabled by default in development'
+ it_behaves_like 'performance bar can be displayed'
end
end
end
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index ae050f36b4a..375bcc9087e 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -35,6 +35,15 @@ describe GroupDescendantsFinder do
expect(finder.execute).to contain_exactly(project)
end
+ it 'does not include projects shared with the group' do
+ project = create(:project, namespace: group)
+ other_project = create(:project)
+ other_project.project_group_links.create(group: group,
+ group_access: ProjectGroupLink::MASTER)
+
+ expect(finder.execute).to contain_exactly(project)
+ end
+
context 'when archived is `true`' do
let(:params) { { archived: 'true' } }
@@ -189,6 +198,17 @@ describe GroupDescendantsFinder do
expect(finder.execute).to contain_exactly(subgroup, matching_project)
end
+ context 'with a small page size' do
+ let(:params) { { filter: 'test', per_page: 1 } }
+
+ it 'contains all the ancestors of a matching subgroup regardless the page size' do
+ subgroup = create(:group, :private, parent: group)
+ matching = create(:group, :private, name: 'testgroup', parent: subgroup)
+
+ expect(finder.execute).to contain_exactly(subgroup, matching)
+ end
+ end
+
it 'does not include the parent itself' do
group.update!(name: 'test')
diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb
index 8ae08656e01..0b3cf7ece5f 100644
--- a/spec/finders/milestones_finder_spec.rb
+++ b/spec/finders/milestones_finder_spec.rb
@@ -21,10 +21,19 @@ describe MilestonesFinder do
expect(result).to contain_exactly(milestone_1, milestone_2)
end
- it 'returns milestones for groups and projects' do
- result = described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
+ context 'milestones for groups and project' do
+ let(:result) do
+ described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
+ end
+
+ it 'returns milestones for groups and projects' do
+ expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
+ end
- expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
+ it 'orders milestones by due date' do
+ expect(result.first).to eq(milestone_1)
+ expect(result.second).to eq(milestone_3)
+ end
end
context 'with filters' do
@@ -61,30 +70,4 @@ describe MilestonesFinder do
expect(result.to_a).to contain_exactly(milestone_1)
end
end
-
- context 'with order' do
- let(:params) do
- {
- project_ids: [project_1.id, project_2.id],
- group_ids: group.id,
- state: 'all'
- }
- end
-
- it "default orders by due date" do
- result = described_class.new(params).execute
-
- expect(result.first).to eq(milestone_1)
- expect(result.second).to eq(milestone_3)
- end
-
- it "orders by parameter" do
- result = described_class.new(params.merge(order: 'id DESC')).execute
-
- expect(result.first).to eq(milestone_4)
- expect(result.second).to eq(milestone_3)
- expect(result.third).to eq(milestone_2)
- expect(result.fourth).to eq(milestone_1)
- end
- end
end
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 04620f6d88c..a030796c54e 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -22,6 +22,13 @@ describe BlobHelper do
expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line" lang="">:type "assem"))</span></code></pre>])
end
+ it 'returns plaintext for long blobs' do
+ stub_const('Blob::MAXIMUM_TEXT_HIGHLIGHT_SIZE', 1)
+ result = helper.highlight(blob_name, blob_content)
+
+ expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line" lang="">(make-pathname :defaults name</span>\n<span id="LC2" class="line" lang="">:type "assem"))</span></code></pre>])
+ end
+
it 'highlights single block' do
expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line" lang="common_lisp"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
<span id="LC2" class="line" lang="common_lisp"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>]
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index d601cbdb39b..7fa665aecdc 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -125,10 +125,10 @@ describe IssuablesHelper do
describe '#updated_at_by' do
let(:user) { create(:user) }
let(:unedited_issuable) { create(:issue) }
- let(:edited_issuable) { create(:issue, last_edited_by: user, created_at: 3.days.ago, updated_at: 2.days.ago, last_edited_at: 2.days.ago) }
+ let(:edited_issuable) { create(:issue, last_edited_by: user, created_at: 3.days.ago, updated_at: 1.day.ago, last_edited_at: 2.days.ago) }
let(:edited_updated_at_by) do
{
- updatedAt: edited_issuable.updated_at.to_time.iso8601,
+ updatedAt: edited_issuable.last_edited_at.to_time.iso8601,
updatedBy: {
name: user.name,
path: user_path(user)
@@ -142,7 +142,7 @@ describe IssuablesHelper do
context 'when updated by a deleted user' do
let(:edited_updated_at_by) do
{
- updatedAt: edited_issuable.updated_at.to_time.iso8601,
+ updatedAt: edited_issuable.last_edited_at.to_time.iso8601,
updatedBy: {
name: User.ghost.name,
path: user_path(User.ghost)
@@ -192,4 +192,33 @@ describe IssuablesHelper do
expect(JSON.parse(helper.issuable_initial_data(issue))).to eq(expected_data)
end
end
+
+ describe '#selected_labels' do
+ context 'if label_name param is a string' do
+ it 'returns a new label with title' do
+ allow(helper).to receive(:params)
+ .and_return(ActionController::Parameters.new(label_name: 'test label'))
+
+ labels = helper.selected_labels
+
+ expect(labels).to be_an(Array)
+ expect(labels.size).to eq(1)
+ expect(labels.first.title).to eq('test label')
+ end
+ end
+
+ context 'if label_name param is an array' do
+ it 'returns a new label with title for each element' do
+ allow(helper).to receive(:params)
+ .and_return(ActionController::Parameters.new(label_name: ['test label 1', 'test label 2']))
+
+ labels = helper.selected_labels
+
+ expect(labels).to be_an(Array)
+ expect(labels.size).to eq(2)
+ expect(labels.first.title).to eq('test label 1')
+ expect(labels.second.title).to eq('test label 2')
+ end
+ end
+ end
end
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index 645ce831b53..e5e7b48228b 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -5,7 +5,7 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-
+import _ from 'underscore';
import '~/boards/models/issue';
import '~/boards/models/label';
import '~/boards/models/list';
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 9ae2d535398..0671facb285 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,5 +1,6 @@
/* global BoardService */
/* eslint-disable comma-dangle, no-unused-vars, quote-props */
+import _ from 'underscore';
export const listObj = {
id: 300,
diff --git a/spec/javascripts/boards/utils/query_data_spec.js b/spec/javascripts/boards/utils/query_data_spec.js
new file mode 100644
index 00000000000..922215ffc1d
--- /dev/null
+++ b/spec/javascripts/boards/utils/query_data_spec.js
@@ -0,0 +1,27 @@
+import queryData from '~/boards/utils/query_data';
+
+describe('queryData', () => {
+ it('parses path for label with trailing +', () => {
+ expect(
+ queryData('label_name[]=label%2B', {}),
+ ).toEqual({
+ label_name: ['label+'],
+ });
+ });
+
+ it('parses path for milestone with trailing +', () => {
+ expect(
+ queryData('milestone_title=A%2B', {}),
+ ).toEqual({
+ milestone_title: 'A+',
+ });
+ });
+
+ it('parses path for search terms with spaces', () => {
+ expect(
+ queryData('search=two+words', {}),
+ ).toEqual({
+ search: 'two words',
+ });
+ });
+});
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index 9fc047b1f5e..d62c2966a8b 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
diff --git a/spec/javascripts/commit_merge_requests_spec.js b/spec/javascripts/commit_merge_requests_spec.js
new file mode 100644
index 00000000000..3466ef51ea8
--- /dev/null
+++ b/spec/javascripts/commit_merge_requests_spec.js
@@ -0,0 +1,60 @@
+import * as CommitMergeRequests from '~/commit_merge_requests';
+
+describe('CommitMergeRequests', () => {
+ describe('createContent', () => {
+ it('should return created content', () => {
+ const content1 = CommitMergeRequests.createContent([{ iid: 1, path: '/path1', title: 'foo' }, { iid: 2, path: '/path2', title: 'baz' }])[0];
+ expect(content1.tagName).toEqual('SPAN');
+ expect(content1.childElementCount).toEqual(4);
+
+ const content2 = CommitMergeRequests.createContent([])[0];
+ expect(content2.tagName).toEqual('SPAN');
+ expect(content2.childElementCount).toEqual(0);
+ expect(content2.innerText).toEqual('No related merge requests found');
+ });
+ });
+
+ describe('getHeaderText', () => {
+ it('should return header text', () => {
+ expect(CommitMergeRequests.getHeaderText(0, 1)).toEqual('1 merge request');
+ expect(CommitMergeRequests.getHeaderText(0, 2)).toEqual('2 merge requests');
+ expect(CommitMergeRequests.getHeaderText(1, 1)).toEqual(',');
+ expect(CommitMergeRequests.getHeaderText(1, 2)).toEqual(',');
+ });
+ });
+
+ describe('createHeader', () => {
+ it('should return created header', () => {
+ const header = CommitMergeRequests.createHeader(0, 1)[0];
+ expect(header.tagName).toEqual('SPAN');
+ expect(header.innerText).toEqual('1 merge request');
+ });
+ });
+
+ describe('createItem', () => {
+ it('should return created item', () => {
+ const item = CommitMergeRequests.createItem({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(item.tagName).toEqual('SPAN');
+ expect(item.childElementCount).toEqual(2);
+ expect(item.children[0].tagName).toEqual('A');
+ expect(item.children[1].tagName).toEqual('SPAN');
+ });
+ });
+
+ describe('createLink', () => {
+ it('should return created link', () => {
+ const link = CommitMergeRequests.createLink({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(link.tagName).toEqual('A');
+ expect(link.href).toMatch(/\/path$/);
+ expect(link.innerText).toEqual('!1');
+ });
+ });
+
+ describe('createTitle', () => {
+ it('should return created title', () => {
+ const title = CommitMergeRequests.createTitle({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(title.tagName).toEqual('SPAN');
+ expect(title.innerText).toEqual('foo');
+ });
+ });
+});
diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js
new file mode 100644
index 00000000000..c8b00a4f553
--- /dev/null
+++ b/spec/javascripts/create_item_dropdown_spec.js
@@ -0,0 +1,106 @@
+import CreateItemDropdown from '~/create_item_dropdown';
+
+const DROPDOWN_ITEM_DATA = [{
+ title: 'one',
+ id: 'one',
+ text: 'one',
+}, {
+ title: 'two',
+ id: 'two',
+ text: 'two',
+}, {
+ title: 'three',
+ id: 'three',
+ text: 'three',
+}];
+
+describe('CreateItemDropdown', () => {
+ preloadFixtures('static/create_item_dropdown.html.raw');
+
+ let $wrapperEl;
+
+ beforeEach(() => {
+ loadFixtures('static/create_item_dropdown.html.raw');
+ $wrapperEl = $('.js-create-item-dropdown-fixture-root');
+
+ // eslint-disable-next-line no-new
+ new CreateItemDropdown({
+ $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'),
+ defaultToggleLabel: 'All variables',
+ fieldName: 'variable[environment]',
+ getData: (term, callback) => {
+ callback(DROPDOWN_ITEM_DATA);
+ },
+ });
+ });
+
+ afterEach(() => {
+ $wrapperEl.remove();
+ });
+
+ it('should have a dropdown item for each piece of data', () => {
+ // Get the data in the dropdown
+ $('.js-dropdown-menu-toggle').click();
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length);
+ });
+
+ describe('created items', () => {
+ const NEW_ITEM_TEXT = 'foobarbaz';
+
+ function createItemAndClearInput(text) {
+ // Filter for the new item
+ $wrapperEl.find('.dropdown-input-field')
+ .val(text)
+ .trigger('input');
+
+ // Create the new item
+ const $createButton = $wrapperEl.find('.js-dropdown-create-new-item');
+ $createButton.click();
+
+ // Clear out the filter
+ $wrapperEl.find('.dropdown-input-field')
+ .val('')
+ .trigger('input');
+ }
+
+ beforeEach(() => {
+ // Open the dropdown
+ $('.js-dropdown-menu-toggle').click();
+
+ // Filter for the new item
+ $wrapperEl.find('.dropdown-input-field')
+ .val(NEW_ITEM_TEXT)
+ .trigger('input');
+ });
+
+ it('create new item button should include the filter text', () => {
+ expect($wrapperEl.find('.js-dropdown-create-new-item code').text()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should update the dropdown with the newly created item', () => {
+ // Create the new item
+ const $createButton = $wrapperEl.find('.js-dropdown-create-new-item');
+ $createButton.click();
+
+ expect($wrapperEl.find('.dropdown-toggle-text').text()).toEqual(NEW_ITEM_TEXT);
+ expect($wrapperEl.find('input[name="variable[environment]"]').val()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should include newly created item in dropdown list', () => {
+ createItemAndClearInput(NEW_ITEM_TEXT);
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(1 + DROPDOWN_ITEM_DATA.length);
+ expect($($itemEls.get(DROPDOWN_ITEM_DATA.length)).text()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should not duplicate an item when trying to create an existing item', () => {
+ createItemAndClearInput(DROPDOWN_ITEM_DATA[0].text);
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length);
+ });
+ });
+});
diff --git a/spec/javascripts/deploy_keys/components/app_spec.js b/spec/javascripts/deploy_keys/components/app_spec.js
index 0ca9290d3d2..b870f87eab9 100644
--- a/spec/javascripts/deploy_keys/components/app_spec.js
+++ b/spec/javascripts/deploy_keys/components/app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import eventHub from '~/deploy_keys/eventhub';
import deployKeysApp from '~/deploy_keys/components/app.vue';
diff --git a/spec/javascripts/deploy_keys/components/key_spec.js b/spec/javascripts/deploy_keys/components/key_spec.js
index 2f28c5bbf01..b7aadf604a4 100644
--- a/spec/javascripts/deploy_keys/components/key_spec.js
+++ b/spec/javascripts/deploy_keys/components/key_spec.js
@@ -53,18 +53,24 @@ describe('Deploy keys key', () => {
).toBe('Remove');
});
- it('shows write access text when key has write access', (done) => {
- vm.deployKey.can_push = true;
+ it('shows write access title when key has write access', (done) => {
+ vm.deployKey.deploy_keys_projects[0].can_push = true;
Vue.nextTick(() => {
expect(
- vm.$el.querySelector('.write-access-allowed'),
- ).not.toBeNull();
-
- expect(
- vm.$el.querySelector('.write-access-allowed').textContent.trim(),
+ vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
).toBe('Write access allowed');
+ done();
+ });
+ });
+
+ it('does not show write access title when key has write access', (done) => {
+ vm.deployKey.deploy_keys_projects[0].can_push = false;
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
+ ).toBe('Read access only');
done();
});
});
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index d02adb25b4e..a41a4e5a3f7 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import environmentsComponent from '~/environments/components/environments_app.vue';
import { environment, folder } from './mock_data';
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 4ea4d9d7499..a085074d312 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import { environmentsList } from '../mock_data';
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index 2ecb64d84b5..0684c3498a2 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import AjaxCache from '~/lib/utils/ajax_cache';
import UsersCache from '~/lib/utils/users_cache';
diff --git a/spec/javascripts/fixtures/create_item_dropdown.html.haml b/spec/javascripts/fixtures/create_item_dropdown.html.haml
new file mode 100644
index 00000000000..d4d91b93caf
--- /dev/null
+++ b/spec/javascripts/fixtures/create_item_dropdown.html.haml
@@ -0,0 +1,13 @@
+.js-create-item-dropdown-fixture-root
+ %input{ name: 'variable[environment]', type: 'hidden' }
+ = dropdown_tag('some label',
+ options: { toggle_class: 'js-dropdown-menu-toggle',
+ content_class: 'js-dropdown-content',
+ filter: true,
+ dropdown_class: "dropdown-menu-selectable",
+ footer_content: true }) do
+ %ul.dropdown-footer-list
+ %li
+ %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item" }
+ Create wildcard
+ %code
diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb
new file mode 100644
index 00000000000..703cd3d49fa
--- /dev/null
+++ b/spec/javascripts/fixtures/search.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe SearchController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('search/')
+ end
+
+ it 'search/show.html.raw' do |example|
+ get :show
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js
index 97e3ab682c5..7198dbd4cf2 100644
--- a/spec/javascripts/flash_spec.js
+++ b/spec/javascripts/flash_spec.js
@@ -183,11 +183,15 @@ describe('Flash', () => {
});
it('adds flash element into container', () => {
- flash('test');
+ flash('test', 'alert', document, null, false, true);
expect(
document.querySelector('.flash-alert'),
).not.toBeNull();
+
+ expect(
+ document.body.className,
+ ).toContain('flash-shown');
});
it('adds flash into specified parent', () => {
@@ -220,13 +224,17 @@ describe('Flash', () => {
});
it('removes element after clicking', () => {
- flash('test', 'alert', document, null, false);
+ flash('test', 'alert', document, null, false, true);
document.querySelector('.flash-alert').click();
expect(
document.querySelector('.flash-alert'),
).toBeNull();
+
+ expect(
+ document.body.className,
+ ).not.toContain('flash-shown');
});
describe('with actionConfig', () => {
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 1454ca52018..9280db072b3 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -218,6 +218,39 @@ describe('Issuable output', () => {
});
});
+ describe('shows dialog when issue has unsaved changed', () => {
+ it('confirms on title change', (done) => {
+ vm.showForm = true;
+ vm.state.titleText = 'title has changed';
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).not.toBeNull();
+ done();
+ });
+ });
+
+ it('confirms on description change', (done) => {
+ vm.showForm = true;
+ vm.state.descriptionText = 'description has changed';
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).not.toBeNull();
+ done();
+ });
+ });
+
+ it('does nothing when nothing has changed', (done) => {
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).toBeNull();
+ done();
+ });
+ });
+ });
+
describe('error when updating', () => {
beforeEach(() => {
spyOn(window, 'Flash').and.callThrough();
diff --git a/spec/javascripts/issue_show/components/fields/description_template_spec.js b/spec/javascripts/issue_show/components/fields/description_template_spec.js
index 2b7ee65094b..30441faf844 100644
--- a/spec/javascripts/issue_show/components/fields/description_template_spec.js
+++ b/spec/javascripts/issue_show/components/fields/description_template_spec.js
@@ -1,7 +1,5 @@
import Vue from 'vue';
import descriptionTemplate from '~/issue_show/components/fields/description_template.vue';
-import '~/templates/issuable_template_selector';
-import '~/templates/issuable_template_selectors';
describe('Issue description template component', () => {
let vm;
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index 000b53af016..50ce019c32a 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -1,7 +1,5 @@
import Vue from 'vue';
import formComponent from '~/issue_show/components/form.vue';
-import '~/templates/issuable_template_selector';
-import '~/templates/issuable_template_selectors';
describe('Inline edit form component', () => {
let vm;
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index b740c9ed893..feb341d22e6 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -52,11 +52,6 @@ describe('Job', () => {
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
});
-
- it('displays the remove date correctly', () => {
- const removeDateElement = document.querySelector('.js-artifacts-remove');
- expect(removeDateElement.innerText.trim()).toBe('1 year remaining');
- });
});
describe('running build', () => {
diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js
index 83395ea451e..a9df0418d5d 100644
--- a/spec/javascripts/jobs/header_spec.js
+++ b/spec/javascripts/jobs/header_spec.js
@@ -31,6 +31,7 @@ describe('Job details header', () => {
email: 'foo@bar.com',
avatar_url: 'link',
},
+ started: '2018-01-08T09:48:27.319Z',
new_issue_path: 'path',
},
isLoading: false,
@@ -43,15 +44,32 @@ describe('Job details header', () => {
vm.$destroy();
});
- it('should render provided job information', () => {
- expect(
- vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
- ).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
+ describe('triggered job', () => {
+ beforeEach(() => {
+ vm = mountComponent(HeaderComponent, props);
+ });
+
+ it('should render provided job information', () => {
+ expect(
+ vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+ ).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
+ });
+
+ it('should render new issue link', () => {
+ expect(
+ vm.$el.querySelector('.js-new-issue').getAttribute('href'),
+ ).toEqual(props.job.new_issue_path);
+ });
});
- it('should render new issue link', () => {
- expect(
- vm.$el.querySelector('.js-new-issue').getAttribute('href'),
- ).toEqual(props.job.new_issue_path);
+ describe('created job', () => {
+ it('should render created key', () => {
+ props.job.started = false;
+ vm = mountComponent(HeaderComponent, props);
+
+ expect(
+ vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+ ).toEqual('failed Job #123 created 3 weeks ago by Foo');
+ });
});
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 6f8dad6b835..69a23d7b2f3 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -63,13 +63,13 @@ describe('text_utility', () => {
});
});
- describe('stripeHtml', () => {
+ describe('stripHtml', () => {
it('replaces html tag with the default replacement', () => {
- expect(textUtils.stripeHtml('This is a text with <p>html</p>.')).toEqual('This is a text with html.');
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual('This is a text with html.');
});
it('replaces html tags with the provided replacement', () => {
- expect(textUtils.stripeHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .');
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .');
});
});
});
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
index e983e4de3fc..5d0ee91d977 100644
--- a/spec/javascripts/merge_request_notes_spec.js
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 9d6ea3781bc..bae3219b043 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -70,8 +70,8 @@ import IssuablesHelper from '~/helpers/issuables_helper';
beforeEach(() => {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.el = document.querySelector('.js-issuable-actions');
- const merge = new MergeRequest();
- merge.hideCloseButton();
+ new MergeRequest(); // eslint-disable-line no-new
+ MergeRequest.hideCloseButton();
});
it('hides the dropdown close item and selects the next item', () => {
@@ -90,8 +90,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
beforeEach(() => {
loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
this.el = document.querySelector('.js-issuable-actions');
- const merge = new MergeRequest();
- merge.hideCloseButton();
+ MergeRequest.hideCloseButton();
});
it('hides the close button', () => {
diff --git a/spec/javascripts/notebook/cells/markdown_spec.js b/spec/javascripts/notebook/cells/markdown_spec.js
index a88e9ed3d99..02304bf5d7d 100644
--- a/spec/javascripts/notebook/cells/markdown_spec.js
+++ b/spec/javascripts/notebook/cells/markdown_spec.js
@@ -42,6 +42,18 @@ describe('Markdown component', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
});
+ it('sanitizes output', (done) => {
+ Object.assign(cell, {
+ source: ['[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+Cg==)\n'],
+ });
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('a')).toBeNull();
+
+ done();
+ });
+ });
+
describe('katex', () => {
beforeEach(() => {
json = getJSONFixture('blob/notebook/math.json');
diff --git a/spec/javascripts/notebook/cells/output/html_sanitize_tests.js b/spec/javascripts/notebook/cells/output/html_sanitize_tests.js
new file mode 100644
index 00000000000..d587573fc9e
--- /dev/null
+++ b/spec/javascripts/notebook/cells/output/html_sanitize_tests.js
@@ -0,0 +1,66 @@
+export default {
+ 'protocol-based JS injection: simple, no spaces': {
+ input: '<a href="javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces before': {
+ input: '<a href="javascript :alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces after': {
+ input: '<a href="javascript: alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces before and after': {
+ input: '<a href="javascript : alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: preceding colon': {
+ input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: UTF-8 encoding': {
+ input: '<a href="javascript&#58;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long UTF-8 encoding': {
+ input: '<a href="javascript&#0058;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long UTF-8 encoding without semicolons': {
+ input: '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: hex encoding': {
+ input: '<a href="javascript&#x3A;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long hex encoding': {
+ input: '<a href="javascript&#x003A;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: hex encoding without semicolons': {
+ input: '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: null char': {
+ input: '<a href=java\0script:alert("XSS")>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: invalid URL char': {
+ input: '<img src=java\script:alert("XSS")>', // eslint-disable-line no-useless-escape
+ output: '<img>',
+ },
+ 'protocol-based JS injection: Unicode': {
+ input: '<a href="\u0001java\u0003script:alert(\'XSS\')">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: spaces and entities': {
+ input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'img on error': {
+ input: '<img src="x" onerror="alert(document.domain)" />',
+ output: '<img src="x">',
+ },
+};
diff --git a/spec/javascripts/notebook/cells/output/html_spec.js b/spec/javascripts/notebook/cells/output/html_spec.js
new file mode 100644
index 00000000000..9c5385f2922
--- /dev/null
+++ b/spec/javascripts/notebook/cells/output/html_spec.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import htmlOutput from '~/notebook/cells/output/html.vue';
+import sanitizeTests from './html_sanitize_tests';
+
+describe('html output cell', () => {
+ function createComponent(rawCode) {
+ const Component = Vue.extend(htmlOutput);
+
+ return new Component({
+ propsData: {
+ rawCode,
+ },
+ }).$mount();
+ }
+
+ describe('sanitizes output', () => {
+ Object.keys(sanitizeTests).forEach((key) => {
+ it(key, () => {
+ const test = sanitizeTests[key];
+ const vm = createComponent(test.input);
+ const outputEl = [...vm.$el.querySelectorAll('div')].pop();
+
+ expect(outputEl.innerHTML).toEqual(test.output);
+
+ vm.$destroy();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 20e352dd8bd..104d03377b6 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -139,13 +139,21 @@ describe('issue_comment_form component', () => {
});
describe('event enter', () => {
- it('should save note when cmd/ctrl+enter is pressed', () => {
+ it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleSave').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleSave).toHaveBeenCalled();
});
+
+ it('should save note when ctrl+enter is pressed', () => {
+ spyOn(vm, 'handleSave').and.callThrough();
+ vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
+ vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+
+ expect(vm.handleSave).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index 7c8d6685ee1..36c56cd3862 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import notesApp from '~/notes/components/notes_app.vue';
import service from '~/notes/services/notes_service';
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 86e9e2a32a9..f841a408d09 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -69,13 +69,20 @@ describe('issue_note_form component', () => {
});
describe('enter', () => {
- it('should submit note', () => {
+ it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleUpdate').and.callThrough();
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
+ it('should save note when ctrl+enter is pressed', () => {
+ spyOn(vm, 'handleUpdate').and.callThrough();
+ vm.$el.querySelector('textarea').value = 'Foo';
+ vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+
+ expect(vm.handleUpdate).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js
index c8a6cb7e612..cb63b64724d 100644
--- a/spec/javascripts/notes/components/noteable_note_spec.js
+++ b/spec/javascripts/notes/components/noteable_note_spec.js
@@ -1,4 +1,4 @@
-
+import _ from 'underscore';
import Vue from 'vue';
import store from '~/notes/stores';
import issueNote from '~/notes/components/noteable_note.vue';
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 167f074fb9b..a40821a5693 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,4 +1,5 @@
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
+import _ from 'underscore';
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize';
import '~/gl_form';
diff --git a/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js b/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
new file mode 100644
index 00000000000..440a6585d57
--- /dev/null
+++ b/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+
+import axios from '~/lib/utils/axios_utils';
+import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
+import * as urlUtility from '~/lib/utils/url_utility';
+
+import mountComponent from '../../../../../helpers/vue_mount_component_helper';
+
+describe('stop_jobs_modal.vue', () => {
+ const props = {
+ url: `${gl.TEST_HOST}/stop_jobs_modal.vue/stopAll`,
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ beforeEach(() => {
+ const Component = Vue.extend(stopJobsModal);
+ vm = mountComponent(Component, props);
+ });
+
+ describe('onSubmit', () => {
+ it('stops jobs and redirects to overview page', (done) => {
+ const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+ spyOn(axios, 'post').and.callFake((url) => {
+ expect(url).toBe(props.url);
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+
+ vm.onSubmit()
+ .then(() => {
+ expect(redirectSpy).toHaveBeenCalledWith(responseURL);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays error if stopping jobs failed', (done) => {
+ const dummyError = new Error('stopping jobs failed');
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+ spyOn(axios, 'post').and.callFake((url) => {
+ expect(url).toBe(props.url);
+ return Promise.reject(dummyError);
+ });
+
+ vm.onSubmit()
+ .then(done.fail)
+ .catch((error) => {
+ expect(error).toBe(dummyError);
+ expect(redirectSpy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/async_button_spec.js b/spec/javascripts/pipelines/async_button_spec.js
index 48620898357..d010d897642 100644
--- a/spec/javascripts/pipelines/async_button_spec.js
+++ b/spec/javascripts/pipelines/async_button_spec.js
@@ -13,7 +13,7 @@ describe('Pipelines Async Button', () => {
propsData: {
endpoint: '/foo',
title: 'Foo',
- icon: 'fa fa-foo',
+ icon: 'repeat',
cssClass: 'bar',
},
}).$mount();
@@ -23,8 +23,8 @@ describe('Pipelines Async Button', () => {
expect(component.$el.tagName).toEqual('BUTTON');
});
- it('should render the provided icon', () => {
- expect(component.$el.querySelector('i').getAttribute('class')).toContain('fa fa-foo');
+ it('should render svg icon', () => {
+ expect(component.$el.querySelector('svg')).not.toBeNull();
});
it('should render the provided title', () => {
diff --git a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
index 9fec2f61f78..bc6413a159f 100644
--- a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
+++ b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import PipelineMediator from '~/pipelines/pipeline_details_mediatior';
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 367b42cefb0..a99ebc4e51a 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import pipelinesComp from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 1b96b2e3d51..61c2f783acc 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import stage from '~/pipelines/components/stage.vue';
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 87259fe0bab..6a8a85e3dfb 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import registry from '~/registry/components/app.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
index 0b0d34f072a..853ef7f3224 100644
--- a/spec/javascripts/repo/stores/actions_spec.js
+++ b/spec/javascripts/repo/stores/actions_spec.js
@@ -300,19 +300,6 @@ describe('Multi-file store actions', () => {
}).catch(done.fail);
});
- it('closes all files', (done) => {
- store.state.openFiles.push(file());
- store.state.openFiles[0].opened = true;
-
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(Vue.nextTick)
- .then(() => {
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- }).catch(done.fail);
- });
-
it('scrolls to top of page', (done) => {
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js
new file mode 100644
index 00000000000..38e94d45e55
--- /dev/null
+++ b/spec/javascripts/search_spec.js
@@ -0,0 +1,40 @@
+import Api from '~/api';
+import Search from '~/pages/search/show/search';
+
+describe('Search', () => {
+ const fixturePath = 'search/show.html.raw';
+ const searchTerm = 'some search';
+ const fillDropdownInput = (dropdownSelector) => {
+ const dropdownElement = document.querySelector(dropdownSelector).parentNode;
+ const inputElement = dropdownElement.querySelector('.dropdown-input-field');
+ inputElement.value = searchTerm;
+ return inputElement;
+ };
+
+ preloadFixtures(fixturePath);
+
+ beforeEach(() => {
+ loadFixtures(fixturePath);
+ new Search(); // eslint-disable-line no-new
+ });
+
+ it('requests groups from backend when filtering', (done) => {
+ spyOn(Api, 'groups').and.callFake((term) => {
+ expect(term).toBe(searchTerm);
+ done();
+ });
+ const inputElement = fillDropdownInput('.js-search-group-dropdown');
+
+ $(inputElement).trigger('input');
+ });
+
+ it('requests projects from backend when filtering', (done) => {
+ spyOn(Api, 'projects').and.callFake((term) => {
+ expect(term).toBe(searchTerm);
+ done();
+ });
+ const inputElement = fillDropdownInput('.js-search-project-dropdown');
+
+ $(inputElement).trigger('input');
+ });
+});
diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js
index b97e24d9dcf..6bb6d639f24 100644
--- a/spec/javascripts/sidebar/sidebar_assignees_spec.js
+++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees';
import SidebarMediator from '~/sidebar/sidebar_mediator';
diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js
index 9efd109b996..afa18cc127e 100644
--- a/spec/javascripts/sidebar/sidebar_mediator_spec.js
+++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import SidebarMediator from '~/sidebar/sidebar_mediator';
diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
index 8b0d51bbcc8..97f762d07a7 100644
--- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js
+++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js
index 9b33dd02fb9..79db05f04ed 100644
--- a/spec/javascripts/sidebar/subscriptions_spec.js
+++ b/spec/javascripts/sidebar/subscriptions_spec.js
@@ -20,23 +20,23 @@ describe('Subscriptions', function () {
subscribed: undefined,
});
- expect(vm.$refs.loadingButton.loading).toBe(true);
- expect(vm.$refs.loadingButton.label).toBeUndefined();
+ expect(vm.$refs.toggleButton.isLoading).toBe(true);
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-loading');
});
- it('has "Subscribe" text when currently not subscribed', () => {
+ it('is toggled "off" when currently not subscribed', () => {
vm = mountComponent(Subscriptions, {
subscribed: false,
});
- expect(vm.$refs.loadingButton.label).toBe('Subscribe');
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).not.toHaveClass('is-checked');
});
- it('has "Unsubscribe" text when currently not subscribed', () => {
+ it('is toggled "on" when currently subscribed', () => {
vm = mountComponent(Subscriptions, {
subscribed: true,
});
- expect(vm.$refs.loadingButton.label).toBe('Unsubscribe');
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-checked');
});
});
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index c4f500788b2..b1b03ef1e09 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -53,6 +53,13 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
expect(memo.readData()).toEqual('#standard');
});
+ it('overrides last selected tab with hash tag when given', () => {
+ window.location.hash = '#ldap';
+ createMemoizer();
+
+ expect(memo.readData()).toEqual('#ldap');
+ });
+
describe('class constructor', () => {
beforeEach(() => {
memo = createMemoizer();
diff --git a/spec/javascripts/smart_interval_spec.js b/spec/javascripts/smart_interval_spec.js
index 1c87fcec245..7265e1b6cb5 100644
--- a/spec/javascripts/smart_interval_spec.js
+++ b/spec/javascripts/smart_interval_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import SmartInterval from '~/smart_interval';
describe('SmartInterval', function () {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 6897c991066..2f6691df9cd 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -1,6 +1,5 @@
/* eslint-disable jasmine/no-global-setup */
import $ from 'jquery';
-import _ from 'underscore';
import 'jasmine-jquery';
import '~/commons';
@@ -31,7 +30,6 @@ jasmine.getJSONFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
// globalize common libraries
window.$ = window.jQuery = $;
-window._ = _;
// stub expected globals
window.gl = window.gl || {};
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
index 4869fb17d96..f98ebdb38e6 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
@@ -1,18 +1,31 @@
import Vue from 'vue';
-import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived';
+import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetArchived', () => {
- describe('template', () => {
- it('should have correct elements', () => {
- const Component = Vue.extend(archivedComponent);
- const el = new Component({
- el: document.createElement('div'),
- }).$el;
+ let vm;
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy();
- expect(el.querySelector('button').disabled).toBeTruthy();
- expect(el.innerText).toContain('This project is archived, write access has been disabled');
- });
+ beforeEach(() => {
+ const Component = Vue.extend(archivedComponent);
+ vm = mountComponent(Component);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a ci status failed icon', () => {
+ expect(vm.$el.querySelector('.ci-status-icon')).not.toBeNull();
+ });
+
+ it('renders a disabled button', () => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Merge');
+ });
+
+ it('renders information', () => {
+ expect(
+ vm.$el.querySelector('.bold').textContent.trim(),
+ ).toEqual('This project is archived, write access has been disabled');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
index 6042d7384d5..95c94e95e3a 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -1,32 +1,47 @@
import Vue from 'vue';
-import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed';
-
-const mergeError = 'This is the merge error';
+import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetAutoMergeFailed', () => {
- describe('props', () => {
- it('should have props', () => {
- const mrProp = autoMergeFailedComponent.props.mr;
+ let vm;
+ const mergeError = 'This is the merge error';
- expect(mrProp.type instanceof Object).toBeTruthy();
- expect(mrProp.required).toBeTruthy();
+ beforeEach(() => {
+ const Component = Vue.extend(autoMergeFailedComponent);
+ vm = mountComponent(Component, {
+ mr: { mergeError },
});
});
- describe('template', () => {
- const Component = Vue.extend(autoMergeFailedComponent);
- const vm = new Component({
- el: document.createElement('div'),
- propsData: {
- mr: { mergeError },
- },
- });
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders failed message', () => {
+ expect(vm.$el.textContent).toContain('This merge request failed to be merged automatically');
+ });
+
+ it('renders merge error provided', () => {
+ expect(vm.$el.innerText).toContain(mergeError);
+ });
+
+ it('render refresh button', () => {
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Refresh');
+ });
+
+ it('emits event and shows loading icon when button is clicked', (done) => {
+ spyOn(eventHub, '$emit');
+ vm.$el.querySelector('button').click();
+
+ expect(eventHub.$emit.calls.argsFor(0)[0]).toEqual('MRWidgetUpdateRequested');
- it('should have correct elements', () => {
- expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeFalsy();
- expect(vm.$el.innerText).toContain('This merge request failed to be merged automatically');
- expect(vm.$el.innerText).toContain(mergeError);
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ expect(
+ vm.$el.querySelector('button i').classList,
+ ).toContain('fa-spinner');
+ done();
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 1127576617b..073f26cc78f 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -170,14 +170,14 @@ describe('MRWidgetReadyToMerge', () => {
expect(vm.iconClass).toEqual('success');
});
- it('shows x for failed status', () => {
+ it('shows warning icon for failed status', () => {
vm.mr.hasCI = true;
- expect(vm.iconClass).toEqual('failed');
+ expect(vm.iconClass).toEqual('warning');
});
- it('shows x for merge not allowed', () => {
+ it('shows warning icon for merge not allowed', () => {
vm.mr.hasCI = true;
- expect(vm.iconClass).toEqual('failed');
+ expect(vm.iconClass).toEqual('warning');
});
});
@@ -371,6 +371,10 @@ describe('MRWidgetReadyToMerge', () => {
});
});
+ beforeEach(() => {
+ loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
+ });
+
it('should call start and stop polling when MR merged', (done) => {
spyOn(eventHub, '$emit');
spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
@@ -392,6 +396,47 @@ describe('MRWidgetReadyToMerge', () => {
}, 333);
});
+ it('updates status box', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ const statusBox = document.querySelector('.status-box');
+ expect(statusBox.classList.contains('status-box-mr-merged')).toBeTruthy();
+ expect(statusBox.textContent).toContain('Merged');
+
+ done();
+ });
+ });
+
+ it('hides close button', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy();
+
+ done();
+ });
+ });
+
+ it('updates merge request count badge', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ expect(document.querySelector('.js-merge-counter').textContent).toBe('0');
+
+ done();
+ });
+ });
+
it('should continue polling until MR is merged', (done) => {
spyOn(vm.service, 'poll').and.returnValue(returnPromise('some_other_state'));
spyOn(vm, 'initiateRemoveSourceBranchPolling');
diff --git a/spec/javascripts/vue_shared/components/loading_icon_spec.js b/spec/javascripts/vue_shared/components/loading_icon_spec.js
index 1baf3537741..5cd3466f501 100644
--- a/spec/javascripts/vue_shared/components/loading_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/loading_icon_spec.js
@@ -16,7 +16,8 @@ describe('Loading Icon Component', () => {
).toEqual('fa fa-spin fa-spinner fa-1x');
expect(component.$el.tagName).toEqual('DIV');
- expect(component.$el.classList.contains('text-center')).toEqual(true);
+ expect(component.$el.classList).toContain('text-center');
+ expect(component.$el.classList).toContain('loading-container');
});
it('should render accessibility attributes', () => {
diff --git a/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js b/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js
new file mode 100644
index 00000000000..6940b04573e
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js
@@ -0,0 +1,77 @@
+import Vue from 'vue';
+
+import stackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+const createComponent = (config) => {
+ const Component = Vue.extend(stackedProgressBarComponent);
+ const defaultConfig = Object.assign({}, {
+ successLabel: 'Synced',
+ failureLabel: 'Failed',
+ neutralLabel: 'Out of sync',
+ successCount: 10,
+ failureCount: 5,
+ totalCount: 20,
+ }, config);
+
+ return mountComponent(Component, defaultConfig);
+};
+
+describe('StackedProgressBarComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('neutralCount', () => {
+ it('returns neutralCount based on totalCount, successCount and failureCount', () => {
+ expect(vm.neutralCount).toBe(5); // 20 - 10 - 5
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('getPercent', () => {
+ it('returns percentage from provided count based on `totalCount`', () => {
+ expect(vm.getPercent(10)).toBe(50);
+ });
+ });
+
+ describe('barStyle', () => {
+ it('returns style string based on percentage provided', () => {
+ expect(vm.barStyle(50)).toBe('width: 50%;');
+ });
+ });
+
+ describe('getTooltip', () => {
+ it('returns label string based on label and count provided', () => {
+ expect(vm.getTooltip('Synced', 10)).toBe('Synced: 10');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders container element', () => {
+ expect(vm.$el.classList.contains('stacked-progress-bar')).toBeTruthy();
+ });
+
+ it('renders empty state when count is unavailable', () => {
+ const vmX = createComponent({ totalCount: 0, successCount: 0, failureCount: 0 });
+ expect(vmX.$el.querySelectorAll('.status-unavailable').length).not.toBe(0);
+ vmX.$destroy();
+ });
+
+ it('renders bar elements when count is available', () => {
+ expect(vm.$el.querySelectorAll('.status-green').length).not.toBe(0);
+ expect(vm.$el.querySelectorAll('.status-neutral').length).not.toBe(0);
+ expect(vm.$el.querySelectorAll('.status-red').length).not.toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
index 8450ad9dbcb..adf80d0c2bb 100644
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 45a0bb0650f..8edba1f47a3 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,4 +1,4 @@
-/* global Mousetrap */
+import Mousetrap from 'mousetrap';
import Dropzone from 'dropzone';
import ZenMode from '~/zen_mode';
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index b68301a066a..5100f5737c2 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -194,6 +194,12 @@ describe Backup::Manager do
)
end
+ it 'prints the list of available backups' do
+ expect { subject.unpack }.to raise_error SystemExit
+ expect(progress).to have_received(:puts)
+ .with(a_string_matching('1451606400_2016_01_01_1.2.3\n 1451520000_2015_12_31'))
+ end
+
it 'fails the operation and prints an error' do
expect { subject.unpack }.to raise_error SystemExit
expect(progress).to have_received(:puts)
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index f38f0776303..3ca4652f7cc 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -8,7 +8,8 @@ describe Banzai::Filter::RelativeLinkFilter do
group: group,
project_wiki: project_wiki,
ref: ref,
- requested_path: requested_path
+ requested_path: requested_path,
+ only_path: only_path
})
described_class.call(doc, contexts)
@@ -37,6 +38,7 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { project.commit(ref) }
let(:project_wiki) { nil }
let(:requested_path) { '/' }
+ let(:only_path) { true }
shared_examples :preserve_unchanged do
it 'does not modify any relative URL in anchor' do
@@ -240,26 +242,35 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { nil }
let(:ref) { nil }
let(:requested_path) { nil }
+ let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
+ let(:relative_path) { "/#{project.full_path}#{upload_path}" }
context 'to a project upload' do
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'rewrites the link correctly' do
+ doc = filter(link(upload_path))
+
+ expect(doc.at_css('a')['href']).to eq(absolute_path)
+ end
+ end
+
it 'rebuilds relative URL for a link' do
- doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('a')['href'])
- .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ doc = filter(link(upload_path))
+ expect(doc.at_css('a')['href']).to eq(relative_path)
- doc = filter(nested(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')))
- expect(doc.at_css('a')['href'])
- .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ doc = filter(nested(link(upload_path)))
+ expect(doc.at_css('a')['href']).to eq(relative_path)
end
it 'rebuilds relative URL for an image' do
- doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('img')['src'])
- .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ doc = filter(image(upload_path))
+ expect(doc.at_css('img')['src']).to eq(relative_path)
- doc = filter(nested(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')))
- expect(doc.at_css('img')['src'])
- .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ doc = filter(nested(image(upload_path)))
+ expect(doc.at_css('img')['src']).to eq(relative_path)
end
it 'does not modify absolute URL' do
@@ -267,18 +278,19 @@ describe Banzai::Filter::RelativeLinkFilter do
expect(doc.at_css('a')['href']).to eq 'http://example.com'
end
- it 'supports Unicode filenames' do
+ it 'supports unescaped Unicode filenames' do
path = '/uploads/한글.png'
- escaped = Addressable::URI.escape(path)
+ doc = filter(link(path))
- # Stub these methods so the file doesn't actually need to be in the repo
- allow_any_instance_of(described_class)
- .to receive(:file_exists?).and_return(true)
- allow_any_instance_of(described_class)
- .to receive(:image?).with(path).and_return(true)
+ expect(doc.at_css('a')['href']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
+ end
+ it 'supports escaped Unicode filenames' do
+ path = '/uploads/한글.png'
+ escaped = Addressable::URI.escape(path)
doc = filter(image(escaped))
- expect(doc.at_css('img')['src']).to match "/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png"
+
+ expect(doc.at_css('img')['src']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
end
end
@@ -288,6 +300,17 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:project) { nil }
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'rewrites the link correctly' do
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(absolute_path)
+ end
+ end
+
it 'rewrites the link correctly' do
doc = filter(upload_link)
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index 9596f004052..50d053011b3 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -10,15 +10,23 @@ describe Banzai::Filter::WikiLinkFilter do
it "doesn't rewrite absolute links" do
filtered_link = filter("<a href='http://example.com:8000/'>Link</a>", project_wiki: wiki).children[0]
+
expect(filtered_link.attribute('href').value).to eq('http://example.com:8000/')
end
+ it "doesn't rewrite links to project uploads" do
+ filtered_link = filter("<a href='/uploads/a.test'>Link</a>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('/uploads/a.test')
+ end
+
describe "invalid links" do
invalid_links = ["http://:8080", "http://", "http://:8080/path"]
invalid_links.each do |invalid_link|
it "doesn't rewrite invalid invalid_links like #{invalid_link}" do
filtered_link = filter("<a href='#{invalid_link}'>Link</a>", project_wiki: wiki).children[0]
+
expect(filtered_link.attribute('href').value).to eq(invalid_link)
end
end
diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
new file mode 100644
index 00000000000..726a3c1c83a
--- /dev/null
+++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Gitlab::Auth::BlockedUserTracker do
+ set(:user) { create(:user) }
+
+ describe '.log_if_user_blocked' do
+ it 'does not log if user failed to login due to undefined reason' do
+ expect_any_instance_of(SystemHooksService).not_to receive(:execute_hooks_for)
+
+ expect(described_class.log_if_user_blocked({})).to be_nil
+ end
+
+ it 'gracefully handles malformed environment variables' do
+ env = { 'warden.options' => 'test' }
+
+ expect(described_class.log_if_user_blocked(env)).to be_nil
+ end
+
+ context 'failed login due to blocked user' do
+ let(:env) do
+ {
+ 'warden.options' => { message: User::BLOCKED_MESSAGE },
+ described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'user' => { 'login' => user.username } }
+ }
+ end
+
+ subject { described_class.log_if_user_blocked(env) }
+
+ before do
+ expect_any_instance_of(SystemHooksService).to receive(:execute_hooks_for).with(user, :failed_login)
+ end
+
+ it 'logs a blocked user' do
+ user.block!
+
+ expect(subject).to be_truthy
+ end
+
+ it 'logs a blocked user by e-mail' do
+ user.block!
+ env[described_class::ACTIVE_RECORD_REQUEST_PARAMS]['user']['login'] = user.email
+
+ expect(subject).to be_truthy
+ end
+
+ it 'logs a LDAP blocked user' do
+ user.ldap_block!
+
+ expect(subject).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 4637816570c..2733eef6611 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -76,6 +76,16 @@ describe Gitlab::Auth::UserAuthFinders do
expect(find_user_from_rss_token).to be_nil
end
end
+
+ context 'when the request format is empty' do
+ it 'the method call does not modify the original value' do
+ env['action_dispatch.request.formats'] = nil
+
+ find_user_from_rss_token
+
+ expect(env['action_dispatch.request.formats']).to be_nil
+ end
+ end
end
describe '#find_user_from_access_token' do
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index a6fbec295b5..cc202ce8bca 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -136,8 +136,8 @@ describe Gitlab::Auth do
it 'grants deploy key write permissions' do
project = create(:project)
- key = create(:deploy_key, can_push: true)
- create(:deploy_keys_project, deploy_key: key, project: project)
+ key = create(:deploy_key)
+ create(:deploy_keys_project, :write_access, deploy_key: key, project: project)
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
@@ -146,7 +146,7 @@ describe Gitlab::Auth do
it 'does not grant deploy key write permissions' do
project = create(:project)
- key = create(:deploy_key, can_push: true)
+ key = create(:deploy_key)
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index 98730602863..d21183b668b 100644
--- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -15,6 +15,10 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t
.to receive(:commits_count=).and_return(nil)
end
+ after do
+ [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information)
+ end
+
def diffs_to_hashes(diffs)
diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access)
end
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index 05e2d94cbd6..7549e9941b6 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -217,11 +217,58 @@ describe Gitlab::Ci::Ansi2html do
"#{section_end[0...-5]}</div>"
end
- it "prints light red" do
- text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
- html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}
+ shared_examples 'forbidden char in section_name' do
+ it 'ignores sections' do
+ text = "#{section_start}Some text#{section_end}"
+ html = text.gsub("\033[0K", '').gsub('<', '&lt;')
- expect(convert_html(text)).to eq(html)
+ expect(convert_html(text)).to eq(html)
+ end
+ end
+
+ shared_examples 'a legit section' do
+ let(:text) { "#{section_start}Some text#{section_end}" }
+
+ it 'prints light red' do
+ text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
+ html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}
+
+ expect(convert_html(text)).to eq(html)
+ end
+
+ it 'begins with a section_start html marker' do
+ expect(convert_html(text)).to start_with(section_start_html)
+ end
+
+ it 'ends with a section_end html marker' do
+ expect(convert_html(text)).to end_with(section_end_html)
+ end
+ end
+
+ it_behaves_like 'a legit section'
+
+ context 'section name includes $' do
+ let(:section_name) { 'my_$ection'}
+
+ it_behaves_like 'forbidden char in section_name'
+ end
+
+ context 'section name includes <' do
+ let(:section_name) { '<a_tag>'}
+
+ it_behaves_like 'forbidden char in section_name'
+ end
+
+ context 'section name contains .-_' do
+ let(:section_name) { 'a.Legit-SeCtIoN_namE' }
+
+ it_behaves_like 'a legit section'
+ end
+
+ it 'do not allow XSS injections' do
+ text = "#{section_start}section_end:1:2<script>alert('XSS Hack!');</script>#{section_end}"
+
+ expect(convert_html(text)).not_to include('<script>')
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb
index 5d4de60bc8a..3cbf19bea8b 100644
--- a/spec/lib/gitlab/ci/config/entry/key_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb
@@ -4,6 +4,26 @@ describe Gitlab::Ci::Config::Entry::Key do
let(:entry) { described_class.new(config) }
describe 'validations' do
+ shared_examples 'key with slash' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+
+ it 'reports errors with config value' do
+ expect(entry.errors).to include 'key config cannot contain the "/" character'
+ end
+ end
+
+ shared_examples 'key with only dots' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+
+ it 'reports errors with config value' do
+ expect(entry.errors).to include 'key config cannot be "." or ".."'
+ end
+ end
+
context 'when entry config value is correct' do
let(:config) { 'test' }
@@ -30,6 +50,48 @@ describe Gitlab::Ci::Config::Entry::Key do
end
end
end
+
+ context 'when entry value contains slash' do
+ let(:config) { 'key/with/some/slashes' }
+
+ it_behaves_like 'key with slash'
+ end
+
+ context 'when entry value contains URI encoded slash (%2F)' do
+ let(:config) { 'key%2Fwith%2Fsome%2Fslashes' }
+
+ it_behaves_like 'key with slash'
+ end
+
+ context 'when entry value is a dot' do
+ let(:config) { '.' }
+
+ it_behaves_like 'key with only dots'
+ end
+
+ context 'when entry value is two dots' do
+ let(:config) { '..' }
+
+ it_behaves_like 'key with only dots'
+ end
+
+ context 'when entry value is a URI encoded dot (%2E)' do
+ let(:config) { '%2e' }
+
+ it_behaves_like 'key with only dots'
+ end
+
+ context 'when entry value is two URI encoded dots (%2E)' do
+ let(:config) { '%2E%2e' }
+
+ it_behaves_like 'key with only dots'
+ end
+
+ context 'when entry value is one dot and one URI encoded dot' do
+ let(:config) { '.%2e' }
+
+ it_behaves_like 'key with only dots'
+ end
end
describe '.default' do
diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb
index 8c25f72804b..d612d29e3e0 100644
--- a/spec/lib/gitlab/ci/status/build/action_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/action_spec.rb
@@ -37,16 +37,16 @@ describe Gitlab::Ci::Status::Build::Action do
describe '.matches?' do
subject { described_class.matches?(build, user) }
- context 'when build is an action' do
- let(:build) { create(:ci_build, :manual) }
+ context 'when build is playable action' do
+ let(:build) { create(:ci_build, :playable) }
it 'is a correct match' do
expect(subject).to be true
end
end
- context 'when build is not manual' do
- let(:build) { create(:ci_build) }
+ context 'when build is not playable action' do
+ let(:build) { create(:ci_build, :non_playable) }
it 'does not match' do
expect(subject).to be false
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 43761c2fe0c..1de3a14b809 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1038,7 +1038,7 @@ describe Gitlab::Database::MigrationHelpers do
end
describe '#change_column_type_using_background_migration' do
- let!(:issue) { create(:issue) }
+ let!(:issue) { create(:issue, :closed, closed_at: Time.zone.now) }
let(:issue_model) do
Class.new(ActiveRecord::Base) do
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 07eb5b82d5f..8706c89c147 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -16,6 +16,18 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
shared_examples 'finding blobs' do
+ context 'nil path' do
+ let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, nil) }
+
+ it { expect(blob).to eq(nil) }
+ end
+
+ context 'blank path' do
+ let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, '') }
+
+ it { expect(blob).to eq(nil) }
+ end
+
context 'file in subdir' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") }
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 6a07a3ca8b8..85e6efd7ca2 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -388,6 +388,84 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
end
+
+ describe '.extract_signature' do
+ subject { described_class.extract_signature(repository, commit_id) }
+
+ shared_examples '.extract_signature' do
+ context 'when the commit is signed' do
+ let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
+
+ it 'returns signature and signed text' do
+ signature, signed_text = subject
+
+ expected_signature = <<~SIGNATURE
+ -----BEGIN PGP SIGNATURE-----
+ Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
+ Comment: GPGTools - https://gpgtools.org
+
+ iQEcBAABCgAGBQJTDvaZAAoJEGJ8X1ifRn8XfvYIAMuB0yrbTGo1BnOSoDfyrjb0
+ Kw2EyUzvXYL72B63HMdJ+/0tlSDC6zONF3fc+bBD8z+WjQMTbwFNMRbSSy2rKEh+
+ mdRybOP3xBIMGgEph0/kmWln39nmFQBsPRbZBWoU10VfI/ieJdEOgOphszgryRar
+ TyS73dLBGE9y9NIININVaNISet9D9QeXFqc761CGjh4YIghvPpi+YihMWapGka6v
+ hgKhX+hc5rj+7IEE0CXmlbYR8OYvAbAArc5vJD7UTxAY4Z7/l9d6Ydt9GQ25khfy
+ ANFgltYzlR6evLFmDjssiP/mx/ZMN91AL0ueJ9nNGv411Mu2CUW+tDCaQf35mdc=
+ =j51i
+ -----END PGP SIGNATURE-----
+ SIGNATURE
+
+ expect(signature).to eq(expected_signature.chomp)
+ expect(signature).to be_a_binary_string
+
+ expected_signed_text = <<~SIGNED_TEXT
+ tree 22bfa2fbd217df24731f43ff43a4a0f8db759dae
+ parent ae73cb07c9eeaf35924a10f713b364d32b2dd34f
+ author Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200
+ committer Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200
+
+ Feature added
+
+ Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
+ SIGNED_TEXT
+
+ expect(signed_text).to eq(expected_signed_text)
+ expect(signed_text).to be_a_binary_string
+ end
+ end
+
+ context 'when the commit has no signature' do
+ let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when the commit cannot be found' do
+ let(:commit_id) { Gitlab::Git::BLANK_SHA }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when the commit ID is invalid' do
+ let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e' }
+
+ it 'raises ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context 'with gitaly' do
+ it_behaves_like '.extract_signature'
+ end
+
+ context 'without gitaly', :skip_gitaly_mock do
+ it_behaves_like '.extract_signature'
+ end
+ end
end
describe '#init_from_rugged' do
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
index beef843537d..78e4fbca28e 100644
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb
@@ -158,39 +158,55 @@ describe Gitlab::Git::GitlabProjects do
subject { gl_projects.import_project(import_url, timeout) }
- context 'success import' do
- it 'imports a repo' do
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
+ shared_examples 'importing repository' do
+ context 'success import' do
+ it 'imports a repo' do
+ expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>."
- expect(logger).to receive(:info).with(message)
+ is_expected.to be_truthy
- is_expected.to be_truthy
+ expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy
+ end
+ end
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy
+ context 'already exists' do
+ it "doesn't import" do
+ FileUtils.mkdir_p(tmp_repo_path)
+
+ is_expected.to be_falsy
+ end
end
end
- context 'already exists' do
- it "doesn't import" do
- FileUtils.mkdir_p(tmp_repo_path)
+ context 'when Gitaly import_repository feature is enabled' do
+ it_behaves_like 'importing repository'
+ end
+
+ context 'when Gitaly import_repository feature is disabled', :disable_gitaly do
+ describe 'logging' do
+ it 'imports a repo' do
+ message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>."
+ expect(logger).to receive(:info).with(message)
- is_expected.to be_falsy
+ subject
+ end
end
- end
- context 'timeout' do
- it 'does not import a repo' do
- stub_spawn_timeout(cmd, timeout, nil)
+ context 'timeout' do
+ it 'does not import a repo' do
+ stub_spawn_timeout(cmd, timeout, nil)
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed."
- expect(logger).to receive(:error).with(message)
+ message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed."
+ expect(logger).to receive(:error).with(message)
- is_expected.to be_falsy
+ is_expected.to be_falsy
- expect(gl_projects.output).to eq("Timed out\n")
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
+ expect(gl_projects.output).to eq("Timed out\n")
+ expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
+ end
end
+
+ it_behaves_like 'importing repository'
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index f4e781c599e..aec7cde6df8 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -899,44 +899,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- context "compare results between log_by_walk and log_by_shell" do
- let(:options) { { ref: "master" } }
- let(:commits_by_walk) { repository.log(options).map(&:id) }
- let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:id) }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
-
- context "with limit" do
- let(:options) { { ref: "master", limit: 1 } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with offset" do
- let(:options) { { ref: "master", offset: 1 } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with skip_merges" do
- let(:options) { { ref: "master", skip_merges: true } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with path" do
- let(:options) { { ref: "master", path: "encoding" } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
-
- context "with follow" do
- let(:options) { { ref: "master", path: "encoding", follow: true } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
- end
- end
-
context "where provides 'after' timestamp" do
options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
@@ -1104,14 +1066,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- describe "branch_names_contains" do
- subject { repository.branch_names_contains(SeedRepo::LastCommit::ID) }
-
- it { is_expected.to include('master') }
- it { is_expected.not_to include('feature') }
- it { is_expected.not_to include('fix') }
- end
-
describe '#autocrlf' do
before(:all) do
@repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index eaf74951b0e..90fbef9d248 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -39,7 +39,7 @@ describe Gitlab::Git::RevList do
]
expect(rev_list).to receive(:popen).with(*params) do |*_, lazy_block:|
- lazy_block.call(output.split("\n").lazy)
+ lazy_block.call(output.lines.lazy.map(&:chomp))
end
end
@@ -64,6 +64,15 @@ describe Gitlab::Git::RevList do
expect(rev_list.new_objects(require_path: true)).to eq(%w[sha2])
end
+ it 'can handle non utf-8 paths' do
+ non_utf_char = [0x89].pack("c*").force_encoding("UTF-8")
+ stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1")
+
+ rev_list.new_objects(require_path: true) do |object_ids|
+ expect(object_ids.force).to eq(%w[sha2])
+ end
+ end
+
it 'can yield a lazy enumerator' do
stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 4290fbb0087..2009a8ac48c 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -51,12 +51,12 @@ describe Gitlab::GitAccess do
context 'when the project exists' do
context 'when actor exists' do
context 'when actor is a DeployKey' do
- let(:deploy_key) { create(:deploy_key, user: user, can_push: true) }
+ let(:deploy_key) { create(:deploy_key, user: user) }
let(:actor) { deploy_key }
context 'when the DeployKey has access to the project' do
before do
- deploy_key.projects << project
+ deploy_key.deploy_keys_projects.create(project: project, can_push: true)
end
it 'allows push and pull access' do
@@ -696,15 +696,13 @@ describe Gitlab::GitAccess do
end
describe 'deploy key permissions' do
- let(:key) { create(:deploy_key, user: user, can_push: can_push) }
+ let(:key) { create(:deploy_key, user: user) }
let(:actor) { key }
context 'when deploy_key can push' do
- let(:can_push) { true }
-
context 'when project is authorized' do
before do
- key.projects << project
+ key.deploy_keys_projects.create(project: project, can_push: true)
end
it { expect { push_access_check }.not_to raise_error }
@@ -732,11 +730,9 @@ describe Gitlab::GitAccess do
end
context 'when deploy_key cannot push' do
- let(:can_push) { false }
-
context 'when project is authorized' do
before do
- key.projects << project
+ key.deploy_keys_projects.create(project: project, can_push: false)
end
it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
new file mode 100644
index 00000000000..1c933410bd5
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::ConflictFilesStitcher do
+ describe 'enumeration' do
+ it 'combines segregated ConflictFile messages together' do
+ target_project = create(:project, :repository)
+ target_repository = target_project.repository.raw
+ target_gitaly_repository = target_repository.gitaly_repository
+
+ our_path_1 = 'our/path/1'
+ their_path_1 = 'their/path/1'
+ our_mode_1 = 0744
+ commit_oid_1 = 'f00'
+ content_1 = 'content of the first file'
+
+ our_path_2 = 'our/path/2'
+ their_path_2 = 'their/path/2'
+ our_mode_2 = 0600
+ commit_oid_2 = 'ba7'
+ content_2 = 'content of the second file'
+
+ header_1 = double(repository: target_gitaly_repository, commit_oid: commit_oid_1,
+ our_path: our_path_1, their_path: their_path_1, our_mode: our_mode_1)
+ header_2 = double(repository: target_gitaly_repository, commit_oid: commit_oid_2,
+ our_path: our_path_2, their_path: their_path_2, our_mode: our_mode_2)
+
+ messages = [
+ double(files: [double(header: header_1), double(header: nil, content: content_1[0..5])]),
+ double(files: [double(header: nil, content: content_1[6..-1])]),
+ double(files: [double(header: header_2)]),
+ double(files: [double(header: nil, content: content_2[0..5]), double(header: nil, content: content_2[6..10])]),
+ double(files: [double(header: nil, content: content_2[11..-1])])
+ ]
+
+ conflict_files = described_class.new(messages).to_a
+
+ expect(conflict_files.size).to be(2)
+
+ expect(conflict_files[0].content).to eq(content_1)
+ expect(conflict_files[0].their_path).to eq(their_path_1)
+ expect(conflict_files[0].our_path).to eq(our_path_1)
+ expect(conflict_files[0].our_mode).to be(our_mode_1)
+ expect(conflict_files[0].repository).to eq(target_repository)
+ expect(conflict_files[0].commit_oid).to eq(commit_oid_1)
+
+ expect(conflict_files[1].content).to eq(content_2)
+ expect(conflict_files[1].their_path).to eq(their_path_2)
+ expect(conflict_files[1].our_path).to eq(our_path_2)
+ expect(conflict_files[1].our_mode).to be(our_mode_2)
+ expect(conflict_files[1].repository).to eq(target_repository)
+ expect(conflict_files[1].commit_oid).to eq(commit_oid_2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
index b9641de7eda..e4fe01a671f 100644
--- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
@@ -19,41 +19,12 @@ describe Gitlab::GitalyClient::ConflictsService do
their_commit_oid: their_commit_oid
)
end
- let(:our_path) { 'our/path' }
- let(:their_path) { 'their/path' }
- let(:our_mode) { 0744 }
- let(:header) do
- double(repository: target_gitaly_repository, commit_oid: our_commit_oid,
- our_path: our_path, our_mode: 0744, their_path: their_path)
- end
- let(:response) do
- [
- double(files: [double(header: header), double(content: 'foo', header: nil)]),
- double(files: [double(content: 'bar', header: nil)])
- ]
- end
- let(:file) { subject[0] }
-
- subject { client.list_conflict_files }
it 'sends an RPC request' do
expect_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:list_conflict_files)
- .with(request, kind_of(Hash)).and_return([])
-
- subject
- end
-
- it 'forms a Gitlab::Git::ConflictFile collection from the response' do
- allow_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:list_conflict_files)
- .with(request, kind_of(Hash)).and_return(response)
+ .with(request, kind_of(Hash)).and_return([].to_enum)
- expect(subject.size).to be(1)
- expect(file.content).to eq('foobar')
- expect(file.their_path).to eq(their_path)
- expect(file.our_path).to eq(our_path)
- expect(file.our_mode).to be(our_mode)
- expect(file.repository).to eq(target_repository)
- expect(file.commit_oid).to eq(our_commit_oid)
+ client.list_conflict_files
end
end
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index a6c99bc07d4..e3bf2801406 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -38,8 +38,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -77,8 +77,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User3.signed_commit_signature,
@@ -116,8 +116,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -151,8 +151,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -187,8 +187,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -217,8 +217,8 @@ describe Gitlab::Gpg::Commit do
let!(:commit) { create :commit, project: project, sha: commit_sha }
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
index d6000af0ecd..c034eccf2a6 100644
--- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -26,8 +26,8 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
before do
allow_any_instance_of(Project).to receive(:commit).and_return(commit)
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(signature)
end
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 162b776e107..5cdc5138fda 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -12,30 +12,61 @@ describe Gitlab::ImportExport::FileImporter do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
-
+ allow(SecureRandom).to receive(:hex).and_return('abcd')
setup_files
-
- described_class.import(archive_file: '', shared: shared)
end
after do
FileUtils.rm_rf(export_path)
end
- it 'removes symlinks in root folder' do
- expect(File.exist?(symlink_file)).to be false
- end
+ context 'normal run' do
+ before do
+ described_class.import(archive_file: '', shared: shared)
+ end
- it 'removes hidden symlinks in root folder' do
- expect(File.exist?(hidden_symlink_file)).to be false
- end
+ it 'removes symlinks in root folder' do
+ expect(File.exist?(symlink_file)).to be false
+ end
+
+ it 'removes hidden symlinks in root folder' do
+ expect(File.exist?(hidden_symlink_file)).to be false
+ end
+
+ it 'removes symlinks in subfolders' do
+ expect(File.exist?(subfolder_symlink_file)).to be false
+ end
- it 'removes symlinks in subfolders' do
- expect(File.exist?(subfolder_symlink_file)).to be false
+ it 'does not remove a valid file' do
+ expect(File.exist?(valid_file)).to be true
+ end
+
+ it 'creates the file in the right subfolder' do
+ expect(shared.export_path).to include('test/abcd')
+ end
end
- it 'does not remove a valid file' do
- expect(File.exist?(valid_file)).to be true
+ context 'error' do
+ before do
+ allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError)
+ described_class.import(archive_file: '', shared: shared)
+ end
+
+ it 'removes symlinks in root folder' do
+ expect(File.exist?(symlink_file)).to be false
+ end
+
+ it 'removes hidden symlinks in root folder' do
+ expect(File.exist?(hidden_symlink_file)).to be false
+ end
+
+ it 'removes symlinks in subfolders' do
+ expect(File.exist?(subfolder_symlink_file)).to be false
+ end
+
+ it 'does not remove a valid file' do
+ expect(File.exist?(valid_file)).to be true
+ end
end
def setup_files
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 4afe48e72ad..63997a40d52 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -100,6 +100,25 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
is_expected.to eq(command)
end
end
+
+ context 'when chart values file is present' do
+ let(:install_command) { described_class.new(prometheus.name, chart: prometheus.chart, chart_values_file: prometheus.chart_values_file) }
+ let(:command) do
+ <<~MSG.chomp
+ set -eo pipefail
+ apk add -U ca-certificates openssl >/dev/null
+ wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+ mv /tmp/linux-amd64/helm /usr/bin/
+
+ helm init --client-only >/dev/null
+ helm install #{prometheus.chart} --name #{prometheus.name} --namespace #{namespace.name} -f /data/helm/#{prometheus.name}/config/values.yaml >/dev/null
+ MSG
+ end
+
+ it 'should return appropriate command' do
+ is_expected.to eq(command)
+ end
+ end
end
describe "#pod_name" do
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 906b10b96d4..0b8e97b8948 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -52,18 +52,20 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should include volumes for the container' do
container = subject.generate.spec.containers.first
- expect(container.volumeMounts.first['name']).to eq('config-volume')
- expect(container.volumeMounts.first['mountPath']).to eq('/etc/config')
+ expect(container.volumeMounts.first['name']).to eq('configuration-volume')
+ expect(container.volumeMounts.first['mountPath']).to eq("/data/helm/#{app.name}/config")
end
it 'should include a volume inside the specification' do
spec = subject.generate.spec
- expect(spec.volumes.first['name']).to eq('config-volume')
+ expect(spec.volumes.first['name']).to eq('configuration-volume')
end
it 'should mount configMap specification in the volume' do
spec = subject.generate.spec
- expect(spec.volumes.first.configMap['name']).to eq('values-config')
+ expect(spec.volumes.first.configMap['name']).to eq('values-content-configuration')
+ expect(spec.volumes.first.configMap['items'].first['key']).to eq('values')
+ expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml')
end
end
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
new file mode 100644
index 00000000000..4a43dbb2371
--- /dev/null
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -0,0 +1,156 @@
+require 'spec_helper'
+
+describe Gitlab::Profiler do
+ RSpec::Matchers.define_negated_matcher :not_change, :change
+
+ let(:null_logger) { Logger.new('/dev/null') }
+ let(:private_token) { 'private' }
+
+ describe '.profile' do
+ let(:app) { double(:app) }
+
+ before do
+ allow(ActionDispatch::Integration::Session).to receive(:new).and_return(app)
+ allow(app).to receive(:get)
+ end
+
+ it 'returns a profile result' do
+ expect(described_class.profile('/')).to be_an_instance_of(RubyProf::Profile)
+ end
+
+ it 'uses the custom logger given' do
+ expect(described_class).to receive(:create_custom_logger)
+ .with(null_logger, private_token: anything)
+ .and_call_original
+
+ described_class.profile('/', logger: null_logger)
+ end
+
+ it 'sends a POST request when data is passed' do
+ post_data = '{"a":1}'
+
+ expect(app).to receive(:post).with(anything, post_data, anything)
+
+ described_class.profile('/', post_data: post_data)
+ end
+
+ it 'uses the private_token for auth if given' do
+ expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
+ expect(app).to receive(:get).with('/api/v4/users')
+
+ described_class.profile('/', private_token: private_token)
+ end
+
+ it 'uses the user for auth if given' do
+ user = double(:user)
+ user_token = 'user'
+
+ allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck, :first).and_return(user_token)
+
+ expect(app).to receive(:get).with('/', nil, 'Private-Token' => user_token)
+ expect(app).to receive(:get).with('/api/v4/users')
+
+ described_class.profile('/', user: user)
+ end
+
+ it 'uses the private_token for auth if both it and user are set' do
+ user = double(:user)
+ user_token = 'user'
+
+ allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck, :first).and_return(user_token)
+
+ expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
+ expect(app).to receive(:get).with('/api/v4/users')
+
+ described_class.profile('/', user: user, private_token: private_token)
+ end
+ end
+
+ describe '.create_custom_logger' do
+ it 'does nothing when nil is passed' do
+ expect(described_class.create_custom_logger(nil)).to be_nil
+ end
+
+ context 'the new logger' do
+ let(:custom_logger) do
+ described_class.create_custom_logger(null_logger, private_token: private_token)
+ end
+
+ it 'does not affect the existing logger' do
+ expect(null_logger).not_to receive(:debug)
+ expect(custom_logger).to receive(:debug).and_call_original
+
+ custom_logger.debug('Foo')
+ end
+
+ it 'strips out the private token' do
+ expect(custom_logger).to receive(:add) do |severity, _progname, message|
+ expect(severity).to eq(Logger::DEBUG)
+ expect(message).to include('public').and include(described_class::FILTERED_STRING)
+ expect(message).not_to include(private_token)
+ end
+
+ custom_logger.debug("public #{private_token}")
+ end
+
+ it 'tracks model load times by model' do
+ custom_logger.debug('This is not a model load')
+ custom_logger.debug('User Load (1.2ms)')
+ custom_logger.debug('User Load (1.3ms)')
+ custom_logger.debug('Project Load (10.4ms)')
+
+ expect(custom_logger.load_times_by_model).to eq('User' => 2.5,
+ 'Project' => 10.4)
+ end
+
+ it 'logs the backtrace, ignoring lines as appropriate' do
+ # Skip Rails's backtrace cleaning.
+ allow(Rails.backtrace_cleaner).to receive(:clean, &:itself)
+
+ expect(custom_logger).to receive(:add)
+ .with(Logger::DEBUG,
+ anything,
+ a_string_matching(File.basename(__FILE__)))
+ .twice
+
+ expect(custom_logger).not_to receive(:add).with(Logger::DEBUG,
+ anything,
+ a_string_matching('lib/gitlab/profiler.rb'))
+
+ # Force a part of the backtrace to be in the (ignored) profiler source
+ # file.
+ described_class.with_custom_logger(nil) { custom_logger.debug('Foo') }
+ end
+ end
+ end
+
+ describe '.with_custom_logger' do
+ context 'when the logger is set' do
+ it 'uses the replacement logger for the duration of the block' do
+ expect(null_logger).to receive(:debug).and_call_original
+
+ expect { described_class.with_custom_logger(null_logger) { ActiveRecord::Base.logger.debug('foo') } }
+ .to not_change { ActiveRecord::Base.logger }
+ .and not_change { ActionController::Base.logger }
+ .and not_change { ActiveSupport::LogSubscriber.colorize_logging }
+ end
+
+ it 'returns the result of the block' do
+ expect(described_class.with_custom_logger(null_logger) { 2 }).to eq(2)
+ end
+ end
+
+ context 'when the logger is nil' do
+ it 'returns the result of the block' do
+ expect(described_class.with_custom_logger(nil) { 2 }).to eq(2)
+ end
+
+ it 'does not modify the standard Rails loggers' do
+ expect { described_class.with_custom_logger(nil) { } }
+ .to not_change { ActiveRecord::Base.logger }
+ .and not_change { ActionController::Base.logger }
+ .and not_change { ActiveSupport::LogSubscriber.colorize_logging }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 17937726f2c..1ebb0105cf5 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -70,15 +70,6 @@ describe Gitlab::ProjectSearchResults do
subject { described_class.parse_search_result(search_result) }
- it 'can correctly parse filenames including ":"' do
- special_char_result = "\nmaster:testdata/project::function1.yaml-1----\nmaster:testdata/project::function1.yaml:2:test: data1\n"
-
- blob = described_class.parse_search_result(special_char_result)
-
- expect(blob.ref).to eq('master')
- expect(blob.filename).to eq('testdata/project::function1.yaml')
- end
-
it "returns a valid FoundBlob" do
is_expected.to be_an Gitlab::SearchResults::FoundBlob
expect(subject.id).to be_nil
@@ -90,8 +81,32 @@ describe Gitlab::ProjectSearchResults do
expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
end
+ context 'when the matching filename contains a colon' do
+ let(:search_result) { "\nmaster:testdata/project::function1.yaml\x001\x00---\n" }
+
+ it 'returns a valid FoundBlob' do
+ expect(subject.filename).to eq('testdata/project::function1.yaml')
+ expect(subject.basename).to eq('testdata/project::function1')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq('---')
+ end
+ end
+
+ context 'when the matching content contains a number surrounded by colons' do
+ let(:search_result) { "\nmaster:testdata/foo.txt\x001\x00blah:9:blah" }
+
+ it 'returns a valid FoundBlob' do
+ expect(subject.filename).to eq('testdata/foo.txt')
+ expect(subject.basename).to eq('testdata/foo')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq('blah:9:blah')
+ end
+ end
+
context "when filename has extension" do
- let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
+ let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" }
it { expect(subject.path).to eq('CONTRIBUTE.md') }
it { expect(subject.filename).to eq('CONTRIBUTE.md') }
@@ -99,7 +114,7 @@ describe Gitlab::ProjectSearchResults do
end
context "when file under directory" do
- let(:search_result) { "master:a/b/c.md:5:a b c\n" }
+ let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" }
it { expect(subject.path).to eq('a/b/c.md') }
it { expect(subject.filename).to eq('a/b/c.md') }
@@ -144,7 +159,7 @@ describe Gitlab::ProjectSearchResults do
end
it 'finds by content' do
- expect(results).to include("master:Title.md:1:Content\n")
+ expect(results).to include("master:Title.md\x001\x00Content\n")
end
end
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
new file mode 100644
index 00000000000..7c97cee982a
--- /dev/null
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -0,0 +1,158 @@
+require 'spec_helper'
+
+describe Gitlab::Utils::Override do
+ let(:base) { Struct.new(:good) }
+
+ let(:derived) { Class.new(base).tap { |m| m.extend described_class } }
+ let(:extension) { Module.new.tap { |m| m.extend described_class } }
+
+ let(:prepending_class) { base.tap { |m| m.prepend extension } }
+ let(:including_class) { base.tap { |m| m.include extension } }
+
+ let(:klass) { subject }
+
+ def good(mod)
+ mod.module_eval do
+ override :good
+ def good
+ super.succ
+ end
+ end
+
+ mod
+ end
+
+ def bad(mod)
+ mod.module_eval do
+ override :bad
+ def bad
+ true
+ end
+ end
+
+ mod
+ end
+
+ shared_examples 'checking as intended' do
+ it 'checks ok for overriding method' do
+ good(subject)
+ result = klass.new(0).good
+
+ expect(result).to eq(1)
+ described_class.verify!
+ end
+
+ it 'raises NotImplementedError when it is not overriding anything' do
+ expect do
+ bad(subject)
+ klass.new(0).bad
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ shared_examples 'nothing happened' do
+ it 'does not complain when it is overriding something' do
+ good(subject)
+ result = klass.new(0).good
+
+ expect(result).to eq(1)
+ described_class.verify!
+ end
+
+ it 'does not complain when it is not overriding anything' do
+ bad(subject)
+ result = klass.new(0).bad
+
+ expect(result).to eq(true)
+ described_class.verify!
+ end
+ end
+
+ before do
+ # Make sure we're not touching the internal cache
+ allow(described_class).to receive(:extensions).and_return({})
+ end
+
+ describe '#override' do
+ context 'when STATIC_VERIFICATION is set' do
+ before do
+ stub_env('STATIC_VERIFICATION', 'true')
+ end
+
+ context 'when subject is a class' do
+ subject { derived }
+
+ it_behaves_like 'checking as intended'
+ end
+
+ context 'when subject is a module, and class is prepending it' do
+ subject { extension }
+ let(:klass) { prepending_class }
+
+ it_behaves_like 'checking as intended'
+ end
+
+ context 'when subject is a module, and class is including it' do
+ subject { extension }
+ let(:klass) { including_class }
+
+ it 'raises NotImplementedError because it is not overriding it' do
+ expect do
+ good(subject)
+ klass.new(0).good
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
+
+ it 'raises NotImplementedError when it is not overriding anything' do
+ expect do
+ bad(subject)
+ klass.new(0).bad
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
+ end
+ end
+ end
+
+ context 'when STATIC_VERIFICATION is not set' do
+ before do
+ stub_env('STATIC_VERIFICATION', nil)
+ end
+
+ context 'when subject is a class' do
+ subject { derived }
+
+ it_behaves_like 'nothing happened'
+ end
+
+ context 'when subject is a module, and class is prepending it' do
+ subject { extension }
+ let(:klass) { prepending_class }
+
+ it_behaves_like 'nothing happened'
+ end
+
+ context 'when subject is a module, and class is including it' do
+ subject { extension }
+ let(:klass) { including_class }
+
+ it 'does not complain when it is overriding something' do
+ good(subject)
+ result = klass.new(0).good
+
+ expect(result).to eq(0)
+ described_class.verify!
+ end
+
+ it 'does not complain when it is not overriding anything' do
+ bad(subject)
+ result = klass.new(0).bad
+
+ expect(result).to eq(true)
+ described_class.verify!
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index e872a5290c5..bda239b7871 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -17,6 +17,22 @@ describe Gitlab::Utils do
end
end
+ describe '.remove_line_breaks' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:original, :expected) do
+ "foo\nbar\nbaz" | "foobarbaz"
+ "foo\r\nbar\r\nbaz" | "foobarbaz"
+ "foobar" | "foobar"
+ end
+
+ with_them do
+ it "replace line breaks with an empty string" do
+ expect(described_class.remove_line_breaks(original)).to eq(expected)
+ end
+ end
+ end
+
describe '.to_boolean' do
it 'accepts booleans' do
expect(to_boolean(true)).to be(true)
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index cbc8c67da61..7a8e798e3c9 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -71,6 +71,18 @@ describe Notify do
is_expected.to have_html_escaped_body_text issue.description
end
+ it 'does not add a reason header' do
+ is_expected.not_to have_header('X-GitLab-NotificationReason', /.+/)
+ end
+
+ context 'when sent with a reason' do
+ subject { described_class.new_issue_email(issue.assignees.first.id, issue.id, NotificationReason::ASSIGNED) }
+
+ it 'includes the reason in a header' do
+ is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+ end
+
context 'when enabled email_author_in_body' do
before do
stub_application_setting(email_author_in_body: true)
@@ -108,6 +120,14 @@ describe Notify do
is_expected.to have_body_text(project_issue_path(project, issue))
end
end
+
+ context 'when sent with a reason' do
+ subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) }
+
+ it 'includes the reason in a header' do
+ is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+ end
end
describe 'that have been relabeled' do
@@ -226,6 +246,14 @@ describe Notify do
is_expected.to have_html_escaped_body_text merge_request.description
end
+ context 'when sent with a reason' do
+ subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id, NotificationReason::ASSIGNED) }
+
+ it 'includes the reason in a header' do
+ is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+ end
+
context 'when enabled email_author_in_body' do
before do
stub_application_setting(email_author_in_body: true)
@@ -263,6 +291,27 @@ describe Notify do
is_expected.to have_html_escaped_body_text(assignee.name)
end
end
+
+ context 'when sent with a reason' do
+ subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::ASSIGNED) }
+
+ it 'includes the reason in a header' do
+ is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+
+ it 'includes the reason in the footer' do
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::ASSIGNED)
+ is_expected.to have_body_text(text)
+
+ new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::MENTIONED)
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::MENTIONED)
+ expect(new_subject).to have_body_text(text)
+
+ new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, nil)
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(nil)
+ expect(new_subject).to have_body_text(text)
+ end
+ end
end
describe 'that have been relabeled' do
diff --git a/spec/migrations/fix_wrongly_renamed_routes_spec.rb b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
index 5ef10b92a3a..78f8ab7512d 100644
--- a/spec/migrations/fix_wrongly_renamed_routes_spec.rb
+++ b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
@@ -1,29 +1,35 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb')
-describe FixWronglyRenamedRoutes, truncate: true do
+describe FixWronglyRenamedRoutes, :truncate, :migration do
let(:subject) { described_class.new }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:routes_table) { table(:routes) }
let(:broken_namespace) do
- namespace = create(:group, name: 'apiis')
- namespace.route.update_attribute(:path, 'api0is')
- namespace
+ namespaces_table.create!(name: 'apiis', path: 'apiis').tap do |namespace|
+ routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'api0is', path: 'api0is')
+ end
end
+ let(:broken_namespace_route) { routes_table.where(source_type: 'Namespace', source_id: broken_namespace.id).first }
describe '#wrongly_renamed' do
it "includes routes that have names that don't match their namespace" do
broken_namespace
- _other_namespace = create(:group, name: 'api0')
+ other_namespace = namespaces_table.create!(name: 'api0', path: 'api0')
+ routes_table.create!(source_type: 'Namespace', source_id: other_namespace.id, name: 'api0', path: 'api0')
expect(subject.wrongly_renamed.map(&:id))
- .to contain_exactly(broken_namespace.route.id)
+ .to contain_exactly(broken_namespace_route.id)
end
end
describe "#paths_and_corrections" do
it 'finds the wrong path and gets the correction from the namespace' do
broken_namespace
- namespace = create(:group, name: 'uploads-test')
- namespace.route.update_attribute(:path, 'uploads0-test')
+ namespaces_table.create!(name: 'uploads-test', path: 'uploads-test').tap do |namespace|
+ routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'uploads-test', path: 'uploads0-test')
+ end
expected_result = [
{ 'namespace_path' => 'apiis', 'path' => 'api0is' },
@@ -36,38 +42,45 @@ describe FixWronglyRenamedRoutes, truncate: true do
describe '#routes_in_namespace_query' do
it 'includes only the required routes' do
- namespace = create(:group, path: 'hello')
- project = create(:project, namespace: namespace)
- _other_namespace = create(:group, path: 'hello0')
-
- result = Route.where(subject.routes_in_namespace_query('hello'))
-
- expect(result).to contain_exactly(namespace.route, project.route)
+ namespace = namespaces_table.create!(name: 'hello', path: 'hello')
+ namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'hello')
+ project = projects_table.new(name: 'my-project', path: 'my-project', namespace_id: namespace.id).tap do |project|
+ project.save!(validate: false)
+ end
+ routes_table.create!(source_type: 'Project', source_id: project.id, name: 'my-project', path: 'hello/my-project')
+ _other_namespace = namespaces_table.create!(name: 'hello0', path: 'hello0')
+
+ result = routes_table.where(subject.routes_in_namespace_query('hello'))
+ project_route = routes_table.where(source_type: 'Project', source_id: project.id).first
+
+ expect(result).to contain_exactly(namespace_route, project_route)
end
end
describe '#up' do
- let(:broken_project) do
- project = create(:project, namespace: broken_namespace, path: 'broken-project')
- project.route.update_attribute(:path, 'api0is/broken-project')
- project
- end
-
it 'renames incorrectly named routes' do
- broken_project
+ broken_project =
+ projects_table.new(name: 'broken-project', path: 'broken-project', namespace_id: broken_namespace.id).tap do |project|
+ project.save!(validate: false)
+ routes_table.create!(source_type: 'Project', source_id: project.id, name: 'broken-project', path: 'api0is/broken-project')
+ end
subject.up
- expect(broken_project.route.reload.path).to eq('apiis/broken-project')
- expect(broken_namespace.route.reload.path).to eq('apiis')
+ broken_project_route = routes_table.where(source_type: 'Project', source_id: broken_project.id).first
+
+ expect(broken_project_route.path).to eq('apiis/broken-project')
+ expect(broken_namespace_route.reload.path).to eq('apiis')
end
it "doesn't touch namespaces that look like something that should be renamed" do
- namespace = create(:group, path: 'api0')
+ namespaces_table.create!(name: 'apiis', path: 'apiis')
+ namespace = namespaces_table.create!(name: 'hello', path: 'api0')
+ namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'api0')
subject.up
- expect(namespace.route.reload.path).to eq('api0')
+ expect(namespace_route.reload.path).to eq('api0')
end
end
end
diff --git a/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb b/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb
new file mode 100644
index 00000000000..0ff98933d5c
--- /dev/null
+++ b/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20171215113714_populate_can_push_from_deploy_keys_projects.rb')
+
+describe PopulateCanPushFromDeployKeysProjects, :migration do
+ let(:migration) { described_class.new }
+ let(:deploy_keys) { table(:keys) }
+ let(:deploy_keys_projects) { table(:deploy_keys_projects) }
+ let(:projects) { table(:projects) }
+
+ before do
+ deploy_keys.inheritance_column = nil
+
+ projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
+ (1..10).each do |index|
+ deploy_keys.create!(id: index, title: 'dummy', type: 'DeployKey', key: Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com')
+ deploy_keys_projects.create!(id: index, deploy_key_id: index, project_id: 1)
+ end
+ end
+
+ describe '#up' do
+ it 'migrates can_push from deploy_keys to deploy_keys_projects' do
+ deploy_keys.limit(5).update_all(can_push: true)
+
+ expected = deploy_keys.order(:id).pluck(:id, :can_push)
+
+ migration.up
+
+ expect(deploy_keys_projects.order(:id).pluck(:deploy_key_id, :can_push)).to eq expected
+ end
+ end
+
+ describe '#down' do
+ it 'migrates can_push from deploy_keys_projects to deploy_keys' do
+ deploy_keys_projects.limit(5).update_all(can_push: true)
+
+ expected = deploy_keys_projects.order(:id).pluck(:deploy_key_id, :can_push)
+
+ migration.down
+
+ expect(deploy_keys.order(:id).pluck(:id, :can_push)).to eq expected
+ end
+ end
+end
diff --git a/spec/migrations/remove_empty_fork_networks_spec.rb b/spec/migrations/remove_empty_fork_networks_spec.rb
index cf6ae5cda74..7f7ce91378b 100644
--- a/spec/migrations/remove_empty_fork_networks_spec.rb
+++ b/spec/migrations/remove_empty_fork_networks_spec.rb
@@ -3,12 +3,19 @@ require Rails.root.join('db', 'post_migrate', '20171114104051_remove_empty_fork_
describe RemoveEmptyForkNetworks, :migration do
let!(:fork_networks) { table(:fork_networks) }
+ let!(:projects) { table(:projects) }
+ let!(:fork_network_members) { table(:fork_network_members) }
- let(:deleted_project) { create(:project) }
- let!(:empty_network) { create(:fork_network, id: 1, root_project_id: deleted_project.id) }
- let!(:other_network) { create(:fork_network, id: 2, root_project_id: create(:project).id) }
+ let(:deleted_project) { projects.create! }
+ let!(:empty_network) { fork_networks.create!(id: 1, root_project_id: deleted_project.id) }
+ let!(:other_network) { fork_networks.create!(id: 2, root_project_id: projects.create.id) }
before do
+ fork_network_members.create(fork_network_id: empty_network.id,
+ project_id: empty_network.root_project_id)
+ fork_network_members.create(fork_network_id: other_network.id,
+ project_id: other_network.root_project_id)
+
deleted_project.destroy!
end
diff --git a/spec/migrations/update_upload_paths_to_system_spec.rb b/spec/migrations/update_upload_paths_to_system_spec.rb
index d4a1553fb0e..984b428a020 100644
--- a/spec/migrations/update_upload_paths_to_system_spec.rb
+++ b/spec/migrations/update_upload_paths_to_system_spec.rb
@@ -1,53 +1,59 @@
-require "spec_helper"
-require Rails.root.join("db", "post_migrate", "20170317162059_update_upload_paths_to_system.rb")
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170317162059_update_upload_paths_to_system.rb')
-describe UpdateUploadPathsToSystem do
+describe UpdateUploadPathsToSystem, :migration do
let(:migration) { described_class.new }
+ let(:uploads_table) { table(:uploads) }
+ let(:base_upload_attributes) { { size: 42, uploader: 'John Doe' } }
before do
allow(migration).to receive(:say)
end
- describe "#uploads_to_switch_to_new_path" do
- it "contains only uploads with the old path for the correct models" do
- _upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
- _upload_with_system_path = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
- _upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg")
- old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
- group_upload = create(:upload, model: create(:group), path: "uploads/group/avatar.jpg")
+ describe '#uploads_to_switch_to_new_path' do
+ it 'contains only uploads with the old path for the correct models' do
+ _upload_for_other_type = create_upload('Pipeline', 'uploads/ci_pipeline/avatar.jpg')
+ _upload_with_system_path = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
+ _upload_with_other_path = create_upload('Project', 'thelongsecretforafileupload/avatar.jpg')
+ old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
+ group_upload = create_upload('Namespace', 'uploads/group/avatar.jpg')
- expect(Upload.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload)
+ expect(uploads_table.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload)
end
end
- describe "#uploads_to_switch_to_old_path" do
- it "contains only uploads with the new path for the correct models" do
- _upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
- upload_with_system_path = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
- _upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg")
- _old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
+ describe '#uploads_to_switch_to_old_path' do
+ it 'contains only uploads with the new path for the correct models' do
+ _upload_for_other_type = create_upload('Pipeline', 'uploads/ci_pipeline/avatar.jpg')
+ upload_with_system_path = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
+ _upload_with_other_path = create_upload('Project', 'thelongsecretforafileupload/avatar.jpg')
+ _old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
- expect(Upload.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path)
+ expect(uploads_table.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path)
end
end
- describe "#up", :truncate do
- it "updates old upload records to the new path" do
- old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
+ describe '#up' do
+ it 'updates old upload records to the new path' do
+ old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
migration.up
- expect(old_upload.reload.path).to eq("uploads/-/system/project/avatar.jpg")
+ expect(old_upload.reload.path).to eq('uploads/-/system/project/avatar.jpg')
end
end
- describe "#down", :truncate do
- it "updates the new system patsh to the old paths" do
- new_upload = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
+ describe '#down' do
+ it 'updates the new system patsh to the old paths' do
+ new_upload = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
migration.down
- expect(new_upload.reload.path).to eq("uploads/project/avatar.jpg")
+ expect(new_upload.reload.path).to eq('uploads/project/avatar.jpg')
end
end
+
+ def create_upload(type, path)
+ uploads_table.create(base_upload_attributes.merge(model_type: type, path: path))
+ end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 3eaeeebf97d..45a606c1ea8 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -25,6 +25,13 @@ describe Ci::Build do
it { is_expected.to be_a(ArtifactMigratable) }
+ describe 'associations' do
+ it 'has a bidirectional relationship with projects' do
+ expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds)
+ expect(Project.reflect_on_association(:builds).has_inverse?).to eq(:project)
+ end
+ end
+
describe 'callbacks' do
context 'when running after_create callback' do
it 'triggers asynchronous build hooks worker' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 7bef798a782..14d234f6aab 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -28,6 +28,13 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to respond_to :short_sha }
it { is_expected.to delegate_method(:full_path).to(:project).with_prefix }
+ describe 'associations' do
+ it 'has a bidirectional relationship with projects' do
+ expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:pipelines)
+ expect(Project.reflect_on_association(:pipelines).has_inverse?).to eq(:project)
+ end
+ end
+
describe '#source' do
context 'when creating new pipeline' do
let(:pipeline) do
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 38829773599..f2efcd9d0e9 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -151,11 +151,11 @@ describe CommitRange do
.with(commit1, user)
.and_return(true)
- expect(commit1.has_been_reverted?(user, issue)).to eq(true)
+ expect(commit1.has_been_reverted?(user, issue.notes_with_associations)).to eq(true)
end
- it 'returns false a commit has not been reverted' do
- expect(commit1.has_been_reverted?(user, issue)).to eq(false)
+ it 'returns false if the commit has not been reverted' do
+ expect(commit1.has_been_reverted?(user, issue.notes_with_associations)).to eq(false)
end
end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 817254c7d1e..f8a98b43e46 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -439,15 +439,25 @@ eos
end
describe '#uri_type' do
- it 'returns the URI type at the given path' do
- expect(commit.uri_type('files/html')).to be(:tree)
- expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
- expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
- expect(commit.uri_type('files/js/application.js')).to be(:blob)
+ shared_examples 'URI type' do
+ it 'returns the URI type at the given path' do
+ expect(commit.uri_type('files/html')).to be(:tree)
+ expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
+ expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
+ expect(commit.uri_type('files/js/application.js')).to be(:blob)
+ end
+
+ it "returns nil if the path doesn't exists" do
+ expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
+ end
+ end
+
+ context 'when Gitaly commit_tree_entry feature is enabled' do
+ it_behaves_like 'URI type'
end
- it "returns nil if the path doesn't exists" do
- expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
+ context 'when Gitaly commit_tree_entry feature is disabled', :disable_gitaly do
+ it_behaves_like 'URI type'
end
end
@@ -513,4 +523,17 @@ eos
expect(described_class.valid_hash?('a' * 41)).to be false
end
end
+
+ describe '#merge_requests' do
+ let!(:project) { create(:project, :repository) }
+ let!(:merge_request1) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') }
+ let!(:merge_request2) { create(:merge_request, source_project: project, source_branch: 'merged-target', target_branch: 'feature') }
+ let(:commit1) { merge_request1.merge_request_diff.commits.last }
+ let(:commit2) { merge_request1.merge_request_diff.commits.first }
+
+ it 'returns merge_requests that introduced that commit' do
+ expect(commit1.merge_requests).to eq([merge_request1, merge_request2])
+ expect(commit2.merge_requests).to eq([merge_request1])
+ end
+ end
end
diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb
new file mode 100644
index 00000000000..621d2d38eae
--- /dev/null
+++ b/spec/models/concerns/triggerable_hooks_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+RSpec.describe TriggerableHooks do
+ before do
+ class TestableHook < WebHook
+ include TriggerableHooks
+ triggerable_hooks [:push_hooks]
+ end
+ end
+
+ describe 'scopes' do
+ it 'defines a scope for each of the requested triggers' do
+ expect(TestableHook).to respond_to :push_hooks
+ expect(TestableHook).not_to respond_to :tag_push_hooks
+ end
+ end
+
+ describe '.hooks_for' do
+ context 'the model has the required trigger scope' do
+ it 'returns the record' do
+ hook = TestableHook.create!(url: 'http://example.com', push_events: true)
+
+ expect(TestableHook.hooks_for(:push_hooks)).to eq [hook]
+ end
+ end
+
+ context 'the model does not have the required trigger scope' do
+ it 'returns an empty relation' do
+ TestableHook.create!(url: 'http://example.com')
+
+ expect(TestableHook.hooks_for(:tag_push_hooks)).to eq []
+ end
+ end
+
+ context 'the stock scope ".all" is accepted' do
+ it 'returns the record' do
+ hook = TestableHook.create!(url: 'http://example.com')
+
+ expect(TestableHook.hooks_for(:all)).to eq [hook]
+ end
+ end
+ end
+end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 0345fefb254..fca3090ff4a 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -8,7 +8,7 @@ describe DeployKeysProject do
describe "Validation" do
it { is_expected.to validate_presence_of(:project_id) }
- it { is_expected.to validate_presence_of(:deploy_key_id) }
+ it { is_expected.to validate_presence_of(:deploy_key) }
end
describe "Destroying" do
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 0e965f541d8..8bc45715dcd 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -7,7 +7,8 @@ describe SystemHook do
it 'sets defined default parameters' do
attrs = {
push_events: false,
- repository_update_events: true
+ repository_update_events: true,
+ merge_requests_events: false
}
expect(system_hook).to have_attributes(attrs)
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 388120160ab..ea6d6e53ef5 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -29,6 +29,12 @@ describe WebHook do
expect(hook.url).to eq('https://example.com')
end
end
+
+ describe 'token' do
+ it { is_expected.to allow_value("foobar").for(:token) }
+
+ it { is_expected.not_to allow_values("foo\nbar", "foo\r\nbar").for(:token) }
+ end
end
describe 'execute' do
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index d556004eccf..b4249d72fc8 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -15,6 +15,28 @@ describe MergeRequestDiff do
it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') }
end
+ describe '.by_commit_sha' do
+ subject(:by_commit_sha) { described_class.by_commit_sha(sha) }
+
+ let!(:merge_request) { create(:merge_request, :with_diffs) }
+
+ context 'with sha contained in' do
+ let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+
+ it 'returns merge request diffs' do
+ expect(by_commit_sha).to eq([merge_request.merge_request_diff])
+ end
+ end
+
+ context 'with sha not contained in' do
+ let(:sha) { 'b83d6e3' }
+
+ it 'returns empty result' do
+ expect(by_commit_sha).to be_empty
+ end
+ end
+ end
+
describe '#latest' do
let!(:mr) { create(:merge_request, :with_diffs) }
let!(:first_diff) { mr.merge_request_diff }
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 27e9c477d61..c76f32b3989 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -87,6 +87,39 @@ describe MergeRequest do
it { is_expected.to respond_to(:merge_when_pipeline_succeeds) }
end
+ describe '.by_commit_sha' do
+ subject(:by_commit_sha) { described_class.by_commit_sha(sha) }
+
+ let!(:merge_request) { create(:merge_request, :with_diffs) }
+
+ context 'with sha contained in latest merge request diff' do
+ let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+
+ it 'returns merge requests' do
+ expect(by_commit_sha).to eq([merge_request])
+ end
+ end
+
+ context 'with sha contained not in latest merge request diff' do
+ let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+
+ it 'returns empty requests' do
+ latest_merge_request_diff = merge_request.merge_request_diffs.create
+ latest_merge_request_diff.merge_request_diff_commits.where(sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0').delete_all
+
+ expect(by_commit_sha).to be_empty
+ end
+ end
+
+ context 'with sha not contained in' do
+ let(:sha) { 'b83d6e3' }
+
+ it 'returns empty result' do
+ expect(by_commit_sha).to be_empty
+ end
+ end
+ end
+
describe '.in_projects' do
it 'returns the merge requests for a set of projects' do
expect(described_class.in_projects(Project.all)).to eq([subject])
@@ -1030,6 +1063,83 @@ describe MergeRequest do
end
end
+ describe '#can_be_reverted?' do
+ context 'when there is no merged_at for the MR' do
+ before do
+ subject.metrics.update!(merged_at: nil)
+ end
+
+ it 'returns false' do
+ expect(subject.can_be_reverted?(nil)).to be_falsey
+ end
+ end
+
+ context 'when there is no merge_commit for the MR' do
+ before do
+ subject.metrics.update!(merged_at: Time.now.utc)
+ end
+
+ it 'returns false' do
+ expect(subject.can_be_reverted?(nil)).to be_falsey
+ end
+ end
+
+ context 'when the MR has been merged' do
+ before do
+ MergeRequests::MergeService
+ .new(subject.target_project, subject.author)
+ .execute(subject)
+ end
+
+ context 'when there is no revert commit' do
+ it 'returns true' do
+ expect(subject.can_be_reverted?(nil)).to be_truthy
+ end
+ end
+
+ context 'when there is a revert commit' do
+ let(:current_user) { subject.author }
+ let(:branch) { subject.target_branch }
+ let(:project) { subject.target_project }
+
+ let(:revert_commit_id) do
+ params = {
+ commit: subject.merge_commit,
+ branch_name: branch,
+ start_branch: branch
+ }
+
+ Commits::RevertService.new(project, current_user, params).execute[:result]
+ end
+
+ before do
+ project.add_master(current_user)
+
+ ProcessCommitWorker.new.perform(project.id,
+ current_user.id,
+ project.commit(revert_commit_id).to_hash,
+ project.default_branch == branch)
+ end
+
+ context 'when the revert commit is mentioned in a note after the MR was merged' do
+ it 'returns false' do
+ expect(subject.can_be_reverted?(current_user)).to be_falsey
+ end
+ end
+
+ context 'when the revert commit is mentioned in a note before the MR was merged' do
+ before do
+ subject.notes.last.update!(created_at: subject.metrics.merged_at - 1.second)
+ end
+
+ it 'returns true' do
+ expect(subject.can_be_reverted?(current_user)).to be_truthy
+ end
+ end
+ end
+ end
+ end
+
describe '#participants' do
let(:project) { create(:project, :public) }
@@ -1910,38 +2020,44 @@ describe MergeRequest do
end
describe '#rebase_in_progress?' do
- # Create merge request and project before we stub file calls
- before do
- subject
- end
+ shared_examples 'checking whether a rebase is in progress' do
+ let(:repo_path) { subject.source_project.repository.path }
+ let(:rebase_path) { File.join(repo_path, "gitlab-worktree", "rebase-#{subject.id}") }
- it 'returns true when there is a current rebase directory' do
- allow(File).to receive(:exist?).and_return(true)
- allow(File).to receive(:mtime).and_return(Time.now)
+ before do
+ system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{rebase_path} master))
+ end
- expect(subject.rebase_in_progress?).to be_truthy
- end
+ it 'returns true when there is a current rebase directory' do
+ expect(subject.rebase_in_progress?).to be_truthy
+ end
- it 'returns false when there is no rebase directory' do
- allow(File).to receive(:exist?).and_return(false)
+ it 'returns false when there is no rebase directory' do
+ FileUtils.rm_rf(rebase_path)
- expect(subject.rebase_in_progress?).to be_falsey
- end
+ expect(subject.rebase_in_progress?).to be_falsey
+ end
+
+ it 'returns false when the rebase directory has expired' do
+ time = 20.minutes.ago.to_time
+ File.utime(time, time, rebase_path)
+
+ expect(subject.rebase_in_progress?).to be_falsey
+ end
- it 'returns false when the rebase directory has expired' do
- allow(File).to receive(:exist?).and_return(true)
- allow(File).to receive(:mtime).and_return(20.minutes.ago)
+ it 'returns false when the source project has been removed' do
+ allow(subject).to receive(:source_project).and_return(nil)
- expect(subject.rebase_in_progress?).to be_falsey
+ expect(subject.rebase_in_progress?).to be_falsey
+ end
end
- it 'returns false when the source project has been removed' do
- allow(subject).to receive(:source_project).and_return(nil)
- allow(File).to receive(:exist?).and_return(true)
- allow(File).to receive(:mtime).and_return(Time.now)
+ context 'when Gitaly rebase_in_progress is enabled' do
+ it_behaves_like 'checking whether a rebase is in progress'
+ end
- expect(File).not_to have_received(:exist?)
- expect(subject.rebase_in_progress?).to be_falsey
+ context 'when Gitaly rebase_in_progress is enabled', :disable_gitaly do
+ it_behaves_like 'checking whether a rebase is in progress'
end
end
end
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 6a5d0decfec..733086e258f 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -92,6 +92,10 @@ describe MicrosoftTeamsService do
service.hook_data(merge_request, 'open')
end
+ before do
+ project.add_developer(user)
+ end
+
it "calls Microsoft Teams API" do
chat_service.execute(merge_sample_data)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 78223c44999..987be8e8b46 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -3206,4 +3206,23 @@ describe Project do
expect { project.write_repository_config }.not_to raise_error
end
end
+
+ describe '#execute_hooks' do
+ it 'executes the projects hooks with the specified scope' do
+ hook1 = create(:project_hook, merge_requests_events: true, tag_push_events: false)
+ hook2 = create(:project_hook, merge_requests_events: false, tag_push_events: true)
+ project = create(:project, hooks: [hook1, hook2])
+
+ expect_any_instance_of(ProjectHook).to receive(:async_execute).once
+
+ project.execute_hooks({}, :tag_push_hooks)
+ end
+
+ it 'executes the system hooks with the specified scope' do
+ expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with({ data: 'data' }, :merge_request_hooks)
+
+ project = build(:project)
+ project.execute_hooks({ data: 'data' }, :merge_request_hooks)
+ end
+ end
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index e78ed1df821..5cff2af4aca 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -146,6 +146,12 @@ describe ProjectStatistics do
expect(statistics.build_artifacts_size).to be(106365)
end
+
+ it 'calculates related build artifacts by project' do
+ expect(Ci::JobArtifact).to receive(:artifacts_size_for).with(project) { 0 }
+
+ statistics.update_build_artifacts_size
+ end
end
context 'when legacy artifacts are used' do
diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb
index ad3c3a406d9..bfe7a30b96a 100644
--- a/spec/models/push_event_spec.rb
+++ b/spec/models/push_event_spec.rb
@@ -63,12 +63,14 @@ describe PushEvent do
let(:event2) { create(:push_event, project: project) }
let(:event3) { create(:push_event, project: project) }
let(:event4) { create(:push_event, project: project) }
+ let(:event5) { create(:push_event, project: project) }
before do
create(:push_event_payload, event: event1, ref: 'foo', action: :created)
create(:push_event_payload, event: event2, ref: 'bar', action: :created)
- create(:push_event_payload, event: event3, ref: 'baz', action: :removed)
- create(:push_event_payload, event: event4, ref: 'baz', ref_type: :tag)
+ create(:push_event_payload, event: event3, ref: 'qux', action: :created)
+ create(:push_event_payload, event: event4, ref: 'baz', action: :removed)
+ create(:push_event_payload, event: event5, ref: 'baz', ref_type: :tag)
project.repository.create_branch('bar', 'master')
@@ -78,6 +80,16 @@ describe PushEvent do
target_project: project,
source_branch: 'bar'
)
+
+ project.repository.create_branch('qux', 'master')
+
+ create(
+ :merge_request,
+ :closed,
+ source_project: project,
+ target_project: project,
+ source_branch: 'qux'
+ )
end
let(:relation) { described_class.without_existing_merge_requests }
@@ -86,16 +98,20 @@ describe PushEvent do
expect(relation).to include(event1)
end
- it 'does not include events that have a corresponding merge request' do
+ it 'does not include events that have a corresponding open merge request' do
expect(relation).not_to include(event2)
end
+ it 'includes events that has corresponding closed/merged merge requests' do
+ expect(relation).to include(event3)
+ end
+
it 'does not include events for removed refs' do
- expect(relation).not_to include(event3)
+ expect(relation).not_to include(event4)
end
it 'does not include events for pushing to tags' do
- expect(relation).not_to include(event4)
+ expect(relation).not_to include(event5)
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index edd981752d9..baaa9e3ef44 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -358,28 +358,38 @@ describe Repository do
end
describe '#can_be_merged?' do
- context 'mergeable branches' do
- subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
+ shared_examples 'can be merged' do
+ context 'mergeable branches' do
+ subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
- it { is_expected.to be_truthy }
- end
+ it { is_expected.to be_truthy }
+ end
- context 'non-mergeable branches' do
- subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') }
+ context 'non-mergeable branches' do
+ subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') }
- it { is_expected.to be_falsey }
- end
+ it { is_expected.to be_falsey }
+ end
- context 'non merged branch' do
- subject { repository.merged_to_root_ref?('fix') }
+ context 'non merged branch' do
+ subject { repository.merged_to_root_ref?('fix') }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_falsey }
+ end
+
+ context 'non existent branch' do
+ subject { repository.merged_to_root_ref?('non_existent_branch') }
+
+ it { is_expected.to be_nil }
+ end
end
- context 'non existent branch' do
- subject { repository.merged_to_root_ref?('non_existent_branch') }
+ context 'when Gitaly can_be_merged feature is enabled' do
+ it_behaves_like 'can be merged'
+ end
- it { is_expected.to be_nil }
+ context 'when Gitaly can_be_merged feature is disabled', :disable_gitaly do
+ it_behaves_like 'can be merged'
end
end
@@ -647,7 +657,7 @@ describe Repository do
subject { results.first }
it { is_expected.to be_an String }
- it { expect(subject.lines[2]).to eq("master:CHANGELOG:190: - Feature: Replace teams with group membership\n") }
+ it { expect(subject.lines[2]).to eq("master:CHANGELOG\x00190\x00 - Feature: Replace teams with group membership\n") }
end
end
@@ -658,6 +668,18 @@ describe Repository do
expect(results.first).to eq('files/html/500.html')
end
+ it 'ignores leading slashes' do
+ results = repository.search_files_by_name('/files', 'master')
+
+ expect(results.first).to eq('files/html/500.html')
+ end
+
+ it 'properly handles when query is only slashes' do
+ results = repository.search_files_by_name('//', 'master')
+
+ expect(results).to match_array([])
+ end
+
it 'properly handles when query is not present' do
results = repository.search_files_by_name('', 'master')
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index ddad6862a63..88f54fd10e5 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -16,9 +16,76 @@ describe Route do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
+
+ describe '#ensure_permanent_paths' do
+ context 'when the route is not yet persisted' do
+ let(:new_route) { described_class.new(path: 'foo', source: build(:group)) }
+
+ context 'when permanent conflicting redirects exist' do
+ it 'is invalid' do
+ redirect = build(:redirect_route, :permanent, path: 'foo/bar/baz')
+ redirect.save!(validate: false)
+
+ expect(new_route.valid?).to be_falsey
+ expect(new_route.errors.first[1]).to eq('foo has been taken before. Please use another one')
+ end
+ end
+
+ context 'when no permanent conflicting redirects exist' do
+ it 'is valid' do
+ expect(new_route.valid?).to be_truthy
+ end
+ end
+ end
+
+ context 'when path has changed' do
+ before do
+ route.path = 'foo'
+ end
+
+ context 'when permanent conflicting redirects exist' do
+ it 'is invalid' do
+ redirect = build(:redirect_route, :permanent, path: 'foo/bar/baz')
+ redirect.save!(validate: false)
+
+ expect(route.valid?).to be_falsey
+ expect(route.errors.first[1]).to eq('foo has been taken before. Please use another one')
+ end
+ end
+
+ context 'when no permanent conflicting redirects exist' do
+ it 'is valid' do
+ expect(route.valid?).to be_truthy
+ end
+ end
+ end
+
+ context 'when path has not changed' do
+ context 'when permanent conflicting redirects exist' do
+ it 'is valid' do
+ redirect = build(:redirect_route, :permanent, path: 'git_lab/foo/bar')
+ redirect.save!(validate: false)
+
+ expect(route.valid?).to be_truthy
+ end
+ end
+ context 'when no permanent conflicting redirects exist' do
+ it 'is valid' do
+ expect(route.valid?).to be_truthy
+ end
+ end
+ end
+ end
end
describe 'callbacks' do
+ context 'before validation' do
+ it 'calls #delete_conflicting_orphaned_routes' do
+ expect(route).to receive(:delete_conflicting_orphaned_routes)
+ route.valid?
+ end
+ end
+
context 'after update' do
it 'calls #create_redirect_for_old_path' do
expect(route).to receive(:create_redirect_for_old_path)
@@ -318,4 +385,58 @@ describe Route do
end
end
end
+
+ describe '#delete_conflicting_orphaned_routes' do
+ context 'when there is a conflicting route' do
+ let!(:conflicting_group) { create(:group, path: 'foo') }
+
+ before do
+ route.path = conflicting_group.route.path
+ end
+
+ context 'when the route is orphaned' do
+ let!(:offending_route) { conflicting_group.route }
+
+ before do
+ Group.delete(conflicting_group) # Orphan the route
+ end
+
+ it 'deletes the orphaned route' do
+ expect do
+ route.valid?
+ end.to change { described_class.count }.from(2).to(1)
+ end
+
+ it 'passes validation, as usual' do
+ expect(route.valid?).to be_truthy
+ end
+ end
+
+ context 'when the route is not orphaned' do
+ it 'does not delete the conflicting route' do
+ expect do
+ route.valid?
+ end.not_to change { described_class.count }
+ end
+
+ it 'fails validation, as usual' do
+ expect(route.valid?).to be_falsey
+ end
+ end
+ end
+
+ context 'when there are no conflicting routes' do
+ it 'does not delete any routes' do
+ route
+
+ expect do
+ route.valid?
+ end.not_to change { described_class.count }
+ end
+
+ it 'passes validation, as usual' do
+ expect(route.valid?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index ab6678cab38..79f25dc4360 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -280,4 +280,38 @@ describe Service do
expect(KubernetesService.find_by_template).to eq(kubernetes_service)
end
end
+
+ describe '#api_field_names' do
+ let(:fake_service) do
+ Class.new(Service) do
+ def fields
+ [
+ { name: 'token' },
+ { name: 'api_token' },
+ { name: 'key' },
+ { name: 'api_key' },
+ { name: 'password' },
+ { name: 'password_field' },
+ { name: 'safe_field' }
+ ]
+ end
+ end
+ end
+
+ let(:service) do
+ fake_service.new(properties: [
+ { token: 'token-value' },
+ { api_token: 'api_token-value' },
+ { key: 'key-value' },
+ { api_key: 'api_key-value' },
+ { password: 'password-value' },
+ { password_field: 'password_field-value' },
+ { safe_field: 'safe_field-value' }
+ ])
+ end
+
+ it 'filters out sensitive fields' do
+ expect(service.api_field_names).to eq(['safe_field'])
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8d0eaf565a7..762cec9b95e 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -966,6 +966,14 @@ describe User do
expect(described_class.search(user3.username.upcase)).to eq([user3])
end
end
+
+ it 'returns no matches for an empty string' do
+ expect(described_class.search('')).to be_empty
+ end
+
+ it 'returns no matches for nil' do
+ expect(described_class.search(nil)).to be_empty
+ end
end
describe '.search_with_secondary_emails' do
@@ -1020,6 +1028,14 @@ describe User do
it 'does not return users with a matching part of secondary email' do
expect(search_with_secondary_emails(email.email[1..4])).not_to include([email.user])
end
+
+ it 'returns no matches for an empty string' do
+ expect(search_with_secondary_emails('')).to be_empty
+ end
+
+ it 'returns no matches for nil' do
+ expect(search_with_secondary_emails(nil)).to be_empty
+ end
end
describe '.find_by_ssh_key_id' do
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 1f1e6ea17e4..0772b3f2e64 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -110,7 +110,7 @@ describe API::DeployKeys do
end
it 'accepts can_push parameter' do
- key_attrs = attributes_for :write_access_key
+ key_attrs = attributes_for(:another_key).merge(can_push: true)
post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
@@ -160,16 +160,6 @@ describe API::DeployKeys do
expect(json_response['title']).to eq('new title')
expect(json_response['can_push']).to eq(true)
end
-
- it 'updates a private ssh key from projects user has access with correct attributes' do
- create(:deploy_keys_project, project: project2, deploy_key: private_deploy_key)
-
- put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: 'new title', can_push: true }
-
- expect(json_response['id']).to eq(private_deploy_key.id)
- expect(json_response['title']).to eq('new title')
- expect(json_response['can_push']).to eq(true)
- end
end
describe 'DELETE /projects/:id/deploy_keys/:key_id' do
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 6732c99e329..51b70fda148 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -3,24 +3,65 @@ require 'spec_helper'
describe API::Deployments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
- let(:project) { deployment.environment.project }
- let!(:deployment) { create(:deployment) }
before do
project.add_master(user)
end
describe 'GET /projects/:id/deployments' do
+ let(:project) { create(:project) }
+ let!(:deployment_1) { create(:deployment, project: project, iid: 11, ref: 'master', created_at: Time.now) }
+ let!(:deployment_2) { create(:deployment, project: project, iid: 12, ref: 'feature', created_at: 1.day.ago) }
+ let!(:deployment_3) { create(:deployment, project: project, iid: 8, ref: 'feature', created_at: 2.days.ago) }
+
context 'as member of the project' do
- it 'returns projects deployments' do
+ it 'returns projects deployments sorted by id asc' do
get api("/projects/#{project.id}/deployments", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.size).to eq(1)
- expect(json_response.first['iid']).to eq(deployment.iid)
+ expect(json_response.size).to eq(3)
+ expect(json_response.first['iid']).to eq(deployment_1.iid)
expect(json_response.first['sha']).to match /\A\h{40}\z/
+ expect(json_response.second['iid']).to eq(deployment_2.iid)
+ expect(json_response.last['iid']).to eq(deployment_3.iid)
+ end
+
+ describe 'ordering' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:order_by) { nil }
+ let(:sort) { nil }
+
+ subject { get api("/projects/#{project.id}/deployments?order_by=#{order_by}&sort=#{sort}", user) }
+
+ def expect_deployments(ordered_deployments)
+ json_response.each_with_index do |deployment_json, index|
+ expect(deployment_json['id']).to eq(public_send(ordered_deployments[index]).id)
+ end
+ end
+
+ before do
+ subject
+ end
+
+ where(:order_by, :sort, :ordered_deployments) do
+ 'created_at' | 'asc' | [:deployment_3, :deployment_2, :deployment_1]
+ 'created_at' | 'desc' | [:deployment_1, :deployment_2, :deployment_3]
+ 'id' | 'asc' | [:deployment_1, :deployment_2, :deployment_3]
+ 'id' | 'desc' | [:deployment_3, :deployment_2, :deployment_1]
+ 'iid' | 'asc' | [:deployment_3, :deployment_1, :deployment_2]
+ 'iid' | 'desc' | [:deployment_2, :deployment_1, :deployment_3]
+ 'ref' | 'asc' | [:deployment_2, :deployment_3, :deployment_1]
+ 'ref' | 'desc' | [:deployment_1, :deployment_2, :deployment_3]
+ end
+
+ with_them do
+ it 'returns the deployments ordered' do
+ expect_deployments(ordered_deployments)
+ end
+ end
end
end
@@ -34,6 +75,9 @@ describe API::Deployments do
end
describe 'GET /projects/:id/deployments/:deployment_id' do
+ let(:project) { deployment.environment.project }
+ let!(:deployment) { create(:deployment) }
+
context 'as a member of the project' do
it 'returns the projects deployment' do
get api("/projects/#{project.id}/deployments/#{deployment.id}", user)
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 2783c51b8df..884a258fd12 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -317,35 +317,20 @@ describe API::Internal do
end
context "git pull" do
- context "gitaly disabled", :disable_gitaly do
- it "has the correct payload" do
- pull(key, project)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
- expect(json_response["gl_repository"]).to eq("project-#{project.id}")
- expect(json_response["gitaly"]).to be_nil
- expect(user).to have_an_activity_record
- end
- end
-
- context "gitaly enabled" do
- it "has the correct payload" do
- pull(key, project)
+ it "has the correct payload" do
+ pull(key, project)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
- expect(json_response["gl_repository"]).to eq("project-#{project.id}")
- expect(json_response["gitaly"]).not_to be_nil
- expect(json_response["gitaly"]["repository"]).not_to be_nil
- expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
- expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
- expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
- expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
- expect(user).to have_an_activity_record
- end
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
+ expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
+ expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
+ expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
+ expect(user).to have_an_activity_record
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 320217f2032..43218755f4f 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -847,6 +847,15 @@ describe API::Issues, :mailer do
expect(json_response['assignee']['name']).to eq(user2.name)
expect(json_response['assignees'].first['name']).to eq(user2.name)
end
+
+ it 'creates a new project issue when assignee_id is empty' do
+ post api("/projects/#{project.id}/issues", user),
+ title: 'new issue', assignee_id: ''
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['assignee']).to be_nil
+ end
end
context 'single assignee restrictions' do
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 805496e4a54..4dd8deb6404 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -25,8 +25,10 @@ describe API::Jobs do
describe 'GET /projects/:id/jobs' do
let(:query) { Hash.new }
- before do
- get api("/projects/#{project.id}/jobs", api_user), query
+ before do |example|
+ unless example.metadata[:skip_before_request]
+ get api("/projects/#{project.id}/jobs", api_user), query
+ end
end
context 'authorized user' do
@@ -51,6 +53,23 @@ describe API::Jobs do
expect(json_job['pipeline']['status']).to eq job.pipeline.status
end
+ it 'avoids N+1 queries', :skip_before_request do
+ first_build = create(:ci_build, :artifacts, pipeline: pipeline)
+ first_build.runner = create(:ci_runner)
+ first_build.user = create(:user)
+ first_build.save
+
+ 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.runner = create(:ci_runner)
+ second_build.user = create(:user)
+ second_build.save
+
+ expect { go }.not_to exceed_query_limit(control_count)
+ end
+
context 'filter project with one scope element' do
let(:query) { { 'scope' => 'pending' } }
@@ -83,6 +102,10 @@ describe API::Jobs do
expect(response).to have_gitlab_http_status(401)
end
end
+
+ def go
+ get api("/projects/#{project.id}/jobs", api_user), query
+ end
end
describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 5d4f81e07a6..73bd4785b11 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -65,6 +65,16 @@ describe API::Members do
expect(json_response.count).to eq(1)
expect(json_response.first['username']).to eq(master.username)
end
+
+ it 'finds all members with no query specified' do
+ get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: ''
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(2)
+ expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+ end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 4eae3e50602..8e2982f1a5d 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -754,16 +754,28 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(400)
end
- context 'when target_branch is specified' do
+ context 'when target_branch and target_project_id is specified' do
+ let(:params) do
+ { title: 'Test merge_request',
+ target_branch: 'master',
+ source_branch: 'markdown',
+ author: user2,
+ target_project_id: unrelated_project.id }
+ end
+
it 'returns 422 if targeting a different fork' do
- post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request',
- target_branch: 'master',
- source_branch: 'markdown',
- author: user2,
- target_project_id: unrelated_project.id
+ unrelated_project.add_developer(user2)
+
+ post api("/projects/#{forked_project.id}/merge_requests", user2), params
+
expect(response).to have_gitlab_http_status(422)
end
+
+ it 'returns 403 if targeting a different fork which user can not access' do
+ post api("/projects/#{forked_project.id}/merge_requests", user2), params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
end
it "returns 201 when target_branch is specified and for the same project" do
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index 08ea7314bb3..6c05c166bd6 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -14,6 +14,46 @@ describe API::ProjectMilestones do
let(:route) { "/projects/#{project.id}/milestones" }
end
+ describe 'DELETE /projects/:id/milestones/:milestone_id' do
+ let(:guest) { create(:user) }
+ let(:reporter) { create(:user) }
+
+ before do
+ project.add_reporter(reporter)
+ end
+
+ it 'returns 404 response when the project does not exists' do
+ delete api("/projects/999/milestones/#{milestone.id}", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 response when the milestone does not exists' do
+ delete api("/projects/#{project.id}/milestones/999", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "returns 404 from guest user deleting a milestone" do
+ delete api("/projects/#{project.id}/milestones/#{milestone.id}", guest)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "rejects a member with reporter access from deleting a milestone" do
+ delete api("/projects/#{project.id}/milestones/#{milestone.id}", reporter)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'deletes the milestone when the user has developer access to the project' do
+ delete api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+
+ expect(project.milestones.find_by_id(milestone.id)).to be_nil
+ expect(response).to have_gitlab_http_status(204)
+ end
+ end
+
describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
it 'creates an activity event when an milestone is closed' do
expect(Event).to receive(:create!)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index e741ac4b7bd..4a2289ca137 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -1,9 +1,9 @@
require 'rails_helper'
describe API::ProjectSnippets do
- let(:project) { create(:project, :public) }
- let(:user) { create(:user) }
- let(:admin) { create(:admin) }
+ set(:project) { create(:project, :public) }
+ set(:user) { create(:user) }
+ set(:admin) { create(:admin) }
describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do
let(:snippet) { create(:project_snippet, :public, project: project) }
@@ -18,6 +18,13 @@ describe API::ProjectSnippets do
expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
end
+ it 'respects project scoping' do
+ other_project = create(:project)
+
+ get api("/projects/#{other_project.id}/snippets/#{snippet.id}/user_agent_detail", admin)
+ expect(response).to have_gitlab_http_status(404)
+ end
+
it "returns unautorized for non-admin users" do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/user_agent_detail", user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index de1763015fa..97e7ffcd38e 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -150,6 +150,19 @@ describe API::Projects do
expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
end
+ context 'and with_issues_enabled=true' do
+ it 'only returns projects with issues enabled' do
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+
+ get api('/projects?with_issues_enabled=true', user)
+
+ expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).not_to include(project.id)
+ end
+ end
+
it "does not include statistics by default" do
get api('/projects', user)
@@ -352,6 +365,19 @@ describe API::Projects do
let(:current_user) { user2 }
let(:projects) { [public_project] }
end
+
+ context 'and with_issues_enabled=true' do
+ it 'does not return private issue projects' do
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)
+
+ get api('/projects?with_issues_enabled=true', user2)
+
+ expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).not_to include(project.id)
+ end
+ end
end
context 'when authenticated as admin' do
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index 10e6a3c07c8..1d23e023bb6 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -80,6 +80,12 @@ describe API::ProtectedBranches do
it_behaves_like 'protected branch'
end
+
+ context 'when protected branch contains a period' do
+ let(:protected_name) { 'my.feature' }
+
+ it_behaves_like 'protected branch'
+ end
end
context 'when authenticated as a guest' do
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 26d56c04862..236f8d7faf5 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -83,14 +83,14 @@ describe API::Services do
get api("/projects/#{project.id}/services/#{dashed_service}", admin)
expect(response).to have_gitlab_http_status(200)
- expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map)
+ expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
end
it "returns properties of service #{service} other than passwords when authenticated as project owner" do
get api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response).to have_gitlab_http_status(200)
- expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords)
+ expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
end
it "returns error when authenticated but not a project owner" do
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index c7a009e999e..6c57d443cbf 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -36,6 +36,7 @@ describe API::SystemHooks do
expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be false
expect(json_response.first['tag_push_events']).to be false
+ expect(json_response.first['merge_requests_events']).to be false
expect(json_response.first['repository_update_events']).to be true
end
end
@@ -67,11 +68,28 @@ describe API::SystemHooks do
end
it 'sets default values for events' do
- post api('/hooks', admin), url: 'http://mep.mep', enable_ssl_verification: true
+ post api('/hooks', admin), url: 'http://mep.mep'
expect(response).to have_gitlab_http_status(201)
expect(json_response['enable_ssl_verification']).to be true
+ expect(json_response['push_events']).to be false
expect(json_response['tag_push_events']).to be false
+ expect(json_response['merge_requests_events']).to be false
+ end
+
+ it 'sets explicit values for events' do
+ post api('/hooks', admin),
+ url: 'http://mep.mep',
+ enable_ssl_verification: false,
+ push_events: true,
+ tag_push_events: true,
+ merge_requests_events: true
+
+ expect(response).to have_http_status(201)
+ expect(json_response['enable_ssl_verification']).to be false
+ expect(json_response['push_events']).to be true
+ expect(json_response['tag_push_events']).to be true
+ expect(json_response['merge_requests_events']).to be true
end
end
diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb
index a73bb456b52..af9e36a3b29 100644
--- a/spec/requests/api/v3/builds_spec.rb
+++ b/spec/requests/api/v3/builds_spec.rb
@@ -13,10 +13,12 @@ describe API::V3::Builds do
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
- before do
+ before do |example|
create(:ci_build, :skipped, pipeline: pipeline)
- get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
+ unless example.metadata[:skip_before_request]
+ get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
+ end
end
context 'authorized user' do
@@ -40,6 +42,23 @@ describe API::V3::Builds do
expect(json_build['pipeline']['status']).to eq build.pipeline.status
end
+ it 'avoids N+1 queries', :skip_before_request do
+ first_build = create(:ci_build, :artifacts, pipeline: pipeline)
+ first_build.runner = create(:ci_runner)
+ first_build.user = create(:user)
+ first_build.save
+
+ 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.runner = create(:ci_runner)
+ second_build.user = create(:user)
+ second_build.save
+
+ expect { go }.not_to exceed_query_limit(control_count)
+ end
+
context 'filter project with one scope element' do
let(:query) { 'scope=pending' }
@@ -84,6 +103,10 @@ describe API::V3::Builds do
expect(response).to have_gitlab_http_status(401)
end
end
+
+ def go
+ get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
+ end
end
describe 'GET /projects/:id/repository/commits/:sha/builds' do
diff --git a/spec/requests/api/v3/deploy_keys_spec.rb b/spec/requests/api/v3/deploy_keys_spec.rb
index 785bc1eb4ba..501af587ad4 100644
--- a/spec/requests/api/v3/deploy_keys_spec.rb
+++ b/spec/requests/api/v3/deploy_keys_spec.rb
@@ -107,7 +107,7 @@ describe API::V3::DeployKeys do
end
it 'accepts can_push parameter' do
- key_attrs = attributes_for :write_access_key
+ key_attrs = attributes_for(:another_key).merge(can_push: true)
post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs
diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb
index b91782ae511..de4339ecb8b 100644
--- a/spec/requests/api/v3/members_spec.rb
+++ b/spec/requests/api/v3/members_spec.rb
@@ -58,6 +58,16 @@ describe API::V3::Members do
expect(json_response.count).to eq(1)
expect(json_response.first['username']).to eq(master.username)
end
+
+ it 'finds all members with no query specified' do
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer), query: ''
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(2)
+ expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+ end
end
end
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index b8b7d9d1c40..6b748369f0d 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -371,16 +371,28 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(400)
end
- context 'when target_branch is specified' do
+ context 'when target_branch and target_project_id is specified' do
+ let(:params) do
+ { title: 'Test merge_request',
+ target_branch: 'master',
+ source_branch: 'markdown',
+ author: user2,
+ target_project_id: unrelated_project.id }
+ end
+
it 'returns 422 if targeting a different fork' do
- post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request',
- target_branch: 'master',
- source_branch: 'markdown',
- author: user2,
- target_project_id: unrelated_project.id
+ unrelated_project.add_developer(user2)
+
+ post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params
+
expect(response).to have_gitlab_http_status(422)
end
+
+ it 'returns 403 if targeting a different fork which user can not access' do
+ post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
end
it "returns 201 when target_branch is specified and for the same project" do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 5e59bb0d585..bee918a20aa 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -781,11 +781,11 @@ describe 'Git LFS API and storage' do
end
context 'when deploy key has project push access' do
- let(:key) { create(:deploy_key, can_push: true) }
+ let(:key) { create(:deploy_key) }
let(:authorization) { authorize_deploy_key }
let(:update_user_permissions) do
- project.deploy_keys << key
+ project.deploy_keys_projects.create(deploy_key: key, can_push: true)
end
it_behaves_like 'pushes new LFS objects'
diff --git a/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
new file mode 100644
index 00000000000..21fc4584654
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
@@ -0,0 +1,100 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/predicate_memoization'
+
+describe RuboCop::Cop::Gitlab::PredicateMemoization do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ shared_examples('registering offense') do |options|
+ let(:offending_lines) { options[:offending_lines] }
+
+ it 'registers an offense when a predicate method is memoizing via ivar' do
+ inspect_source(source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(offending_lines.size)
+ expect(cop.offenses.map(&:line)).to eq(offending_lines)
+ end
+ end
+ end
+
+ shared_examples('not registering offense') do
+ it 'does not register offenses' do
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when source is a predicate method memoizing via ivar' do
+ it_behaves_like 'registering offense', offending_lines: [3] do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really?
+ @really ||= true
+ end
+ end
+ RUBY
+ end
+ end
+
+ it_behaves_like 'registering offense', offending_lines: [4] do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really?
+ value = true
+ @really ||= value
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ context 'when source is a predicate method using ivar with assignment' do
+ it_behaves_like 'not registering offense' do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really?
+ @really = true
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ context 'when source is a predicate method using local with ||=' do
+ it_behaves_like 'not registering offense' do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really?
+ really ||= true
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ context 'when source is a regular method memoizing via ivar' do
+ it_behaves_like 'not registering offense' do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really
+ @really ||= true
+ end
+ end
+ RUBY
+ end
+ end
+ end
+end
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index d3aefa2c9eb..2bd8162d1b7 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -21,18 +21,21 @@ describe DeployKeyEntity do
user_id: deploy_key.user_id,
title: deploy_key.title,
fingerprint: deploy_key.fingerprint,
- can_push: deploy_key.can_push,
destroyed_when_orphaned: true,
almost_orphaned: false,
created_at: deploy_key.created_at,
updated_at: deploy_key.updated_at,
can_edit: false,
- projects: [
+ deploy_keys_projects: [
{
- id: project.id,
- name: project.name,
- full_path: project_path(project),
- full_name: project.full_name
+ can_push: false,
+ project:
+ {
+ id: project.id,
+ name: project.name,
+ full_path: project_path(project),
+ full_name: project.full_name
+ }
}
]
}
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
index 8809b282127..aa9aba6bdff 100644
--- a/spec/services/labels/promote_service_spec.rb
+++ b/spec/services/labels/promote_service_spec.rb
@@ -85,6 +85,19 @@ describe Labels::PromoteService do
change(project_3.labels, :count).by(-1)
end
+ it 'keeps users\' subscriptions' do
+ user2 = create(:user)
+ project_label_1_1.subscriptions.create(user: user, subscribed: true)
+ project_label_2_1.subscriptions.create(user: user, subscribed: true)
+ project_label_3_2.subscriptions.create(user: user, subscribed: true)
+ project_label_2_1.subscriptions.create(user: user2, subscribed: true)
+
+ expect { service.execute(project_label_1_1) }.to change { Subscription.count }.from(4).to(3)
+
+ expect(new_label.subscribed?(user)).to be_truthy
+ expect(new_label.subscribed?(user2)).to be_truthy
+ end
+
it 'recreates priorities' do
service.execute(project_label_1_1)
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index 623b182b205..75553afc033 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -112,5 +112,24 @@ describe MergeRequests::CreateFromIssueService do
expect(result[:merge_request].assignee).to eq(user)
end
+
+ context 'when ref branch is set' do
+ subject { described_class.new(project, user, issue_iid: issue.iid, ref: 'feature').execute }
+
+ it 'sets the merge request source branch to the new issue branch' do
+ expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
+ end
+
+ it 'sets the merge request target branch to the ref branch' do
+ expect(subject[:merge_request].target_branch).to eq('feature')
+ end
+
+ context 'when ref branch does not exist' do
+ it 'does not create a merge request' do
+ expect { described_class.new(project, user, issue_iid: issue.iid, ref: 'nobr').execute }
+ .not_to change { project.merge_requests.count }
+ end
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index dd8c803a2f7..5d226f34d2d 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -263,5 +263,66 @@ describe MergeRequests::CreateService do
expect(issue_ids).to match_array([first_issue.id, second_issue.id])
end
end
+
+ context 'when source and target projects are different' do
+ let(:target_project) { create(:project) }
+
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ source_branch: 'feature',
+ target_branch: 'master',
+ target_project_id: target_project.id
+ }
+ end
+
+ context 'when user can not access source project' do
+ before do
+ target_project.add_developer(assignee)
+ target_project.add_master(user)
+ end
+
+ it 'raises an error' do
+ expect { described_class.new(project, user, opts).execute }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+
+ context 'when user can not access target project' do
+ before do
+ target_project.add_developer(assignee)
+ target_project.add_master(user)
+ end
+
+ it 'raises an error' do
+ expect { described_class.new(project, user, opts).execute }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+ end
+
+ context 'when user sets source project id' do
+ let(:another_project) { create(:project) }
+
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ source_branch: 'feature',
+ target_branch: 'master',
+ source_project_id: another_project.id
+ }
+ end
+
+ before do
+ project.add_developer(assignee)
+ project.add_master(user)
+ end
+
+ it 'ignores source_project_id' do
+ merge_request = described_class.new(project, user, opts).execute
+
+ expect(merge_request.source_project_id).to eq(project.id)
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index 5f047e61c31..fc1c3d67203 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -36,7 +36,7 @@ describe MergeRequests::RebaseService do
end
end
- context 'when unexpected error occurs' do
+ context 'when unexpected error occurs', :disable_gitaly do
before do
allow(repository).to receive(:run_git!).and_raise('Something went wrong')
end
@@ -53,7 +53,7 @@ describe MergeRequests::RebaseService do
end
end
- context 'with git command failure' do
+ context 'with git command failure', :disable_gitaly do
before do
allow(repository).to receive(:run_git!).and_raise(Gitlab::Git::Repository::GitError, 'Something went wrong')
end
@@ -71,31 +71,41 @@ describe MergeRequests::RebaseService do
end
context 'valid params' do
- before do
- service.execute(merge_request)
- end
+ shared_examples 'successful rebase' do
+ before do
+ service.execute(merge_request)
+ end
- it 'rebases source branch' do
- parent_sha = merge_request.source_project.repository.commit(merge_request.source_branch).parents.first.sha
- target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
- expect(parent_sha).to eq(target_branch_sha)
- end
+ it 'rebases source branch' do
+ parent_sha = merge_request.source_project.repository.commit(merge_request.source_branch).parents.first.sha
+ target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
+ expect(parent_sha).to eq(target_branch_sha)
+ end
- it 'records the new SHA on the merge request' do
- head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
- expect(merge_request.reload.rebase_commit_sha).to eq(head_sha)
+ it 'records the new SHA on the merge request' do
+ head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
+ expect(merge_request.reload.rebase_commit_sha).to eq(head_sha)
+ end
+
+ it 'logs correct author and commiter' do
+ head_commit = merge_request.source_project.repository.commit(merge_request.source_branch)
+
+ expect(head_commit.author_email).to eq('dmitriy.zaporozhets@gmail.com')
+ expect(head_commit.author_name).to eq('Dmitriy Zaporozhets')
+ expect(head_commit.committer_email).to eq(user.email)
+ expect(head_commit.committer_name).to eq(user.name)
+ end
end
- it 'logs correct author and commiter' do
- head_commit = merge_request.source_project.repository.commit(merge_request.source_branch)
+ context 'when Gitaly rebase feature is enabled' do
+ it_behaves_like 'successful rebase'
+ end
- expect(head_commit.author_email).to eq('dmitriy.zaporozhets@gmail.com')
- expect(head_commit.author_name).to eq('Dmitriy Zaporozhets')
- expect(head_commit.committer_email).to eq(user.email)
- expect(head_commit.committer_name).to eq(user.name)
+ context 'when Gitaly rebase feature is disabled', :disable_gitaly do
+ it_behaves_like 'successful rebase'
end
- context 'git commands' do
+ context 'git commands', :disable_gitaly do
it 'sets GL_REPOSITORY env variable when calling git commands' do
expect(repository).to receive(:popen).exactly(3)
.with(anything, anything, hash_including('GL_REPOSITORY'))
@@ -106,27 +116,37 @@ describe MergeRequests::RebaseService do
end
context 'fork' do
- let(:forked_project) do
- fork_project(project, user, repository: true)
+ shared_examples 'successful fork rebase' do
+ let(:forked_project) do
+ fork_project(project, user, repository: true)
+ end
+
+ let(:merge_request_from_fork) do
+ forked_project.repository.create_file(
+ user,
+ 'new-file-to-target',
+ '',
+ message: 'Add new file to target',
+ branch_name: 'master')
+
+ create(:merge_request,
+ source_branch: 'master', source_project: forked_project,
+ target_branch: 'master', target_project: project)
+ end
+
+ it 'rebases source branch' do
+ parent_sha = forked_project.repository.commit(merge_request_from_fork.source_branch).parents.first.sha
+ target_branch_sha = project.repository.commit(merge_request_from_fork.target_branch).sha
+ expect(parent_sha).to eq(target_branch_sha)
+ end
end
- let(:merge_request_from_fork) do
- forked_project.repository.create_file(
- user,
- 'new-file-to-target',
- '',
- message: 'Add new file to target',
- branch_name: 'master')
-
- create(:merge_request,
- source_branch: 'master', source_project: forked_project,
- target_branch: 'master', target_project: project)
+ context 'when Gitaly rebase feature is enabled' do
+ it_behaves_like 'successful fork rebase'
end
- it 'rebases source branch' do
- parent_sha = forked_project.repository.commit(merge_request_from_fork.source_branch).parents.first.sha
- target_branch_sha = project.repository.commit(merge_request_from_fork.target_branch).sha
- expect(parent_sha).to eq(target_branch_sha)
+ context 'when Gitaly rebase feature is disabled', :disable_gitaly do
+ it_behaves_like 'successful fork rebase'
end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 43e2643f709..5c59455e3e1 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe NotificationService, :mailer do
+ include EmailSpec::Matchers
+
let(:notification) { described_class.new }
let(:assignee) { create(:user) }
@@ -31,6 +33,14 @@ describe NotificationService, :mailer do
send_notifications(@u_disabled)
should_not_email_anyone
end
+
+ it 'sends the proper notification reason header' do
+ send_notifications(@u_watcher)
+ should_only_email(@u_watcher)
+ email = find_email_for(@u_watcher)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::MENTIONED)
+ end
end
# Next shared examples are intended to test notifications of "participants"
@@ -511,12 +521,35 @@ describe NotificationService, :mailer do
should_not_email(issue.assignees.first)
end
+ it 'properly prioritizes notification reason' do
+ # have assignee be both assigned and mentioned
+ issue.update_attribute(:description, "/cc #{assignee.to_reference} #{@u_mentioned.to_reference}")
+
+ notification.new_issue(issue, @u_disabled)
+
+ email = find_email_for(assignee)
+ expect(email).to have_header('X-GitLab-NotificationReason', 'assigned')
+
+ email = find_email_for(@u_mentioned)
+ expect(email).to have_header('X-GitLab-NotificationReason', 'mentioned')
+ end
+
+ it 'adds "assigned" reason for assignees if any' do
+ notification.new_issue(issue, @u_disabled)
+
+ email = find_email_for(assignee)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', 'assigned')
+ end
+
it "emails any mentioned users with the mention level" do
issue.description = @u_mentioned.to_reference
notification.new_issue(issue, @u_disabled)
- should_email(@u_mentioned)
+ email = find_email_for(@u_mentioned)
+ expect(email).not_to be_nil
+ expect(email).to have_header('X-GitLab-NotificationReason', 'mentioned')
end
it "emails the author if they've opted into notifications about their activity" do
@@ -620,6 +653,14 @@ describe NotificationService, :mailer do
should_not_email(@u_lazy_participant)
end
+ it 'adds "assigned" reason for new assignee' do
+ notification.reassigned_issue(issue, @u_disabled, [assignee])
+
+ email = find_email_for(assignee)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+
it 'emails previous assignee even if he has the "on mention" notif level' do
issue.assignees = [@u_mentioned]
notification.reassigned_issue(issue, @u_disabled, [@u_watcher])
@@ -910,6 +951,14 @@ describe NotificationService, :mailer do
should_not_email(@u_lazy_participant)
end
+ it 'adds "assigned" reason for assignee, if any' do
+ notification.new_merge_request(merge_request, @u_disabled)
+
+ email = find_email_for(merge_request.assignee)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+
it "emails any mentioned users with the mention level" do
merge_request.description = @u_mentioned.to_reference
@@ -924,6 +973,9 @@ describe NotificationService, :mailer do
notification.new_merge_request(merge_request, merge_request.author)
should_email(merge_request.author)
+
+ email = find_email_for(merge_request.author)
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::OWN_ACTIVITY)
end
it "doesn't email the author if they haven't opted into notifications about their activity" do
@@ -1009,6 +1061,14 @@ describe NotificationService, :mailer do
should_not_email(@u_lazy_participant)
end
+ it 'adds "assigned" reason for new assignee' do
+ notification.reassigned_merge_request(merge_request, merge_request.author)
+
+ email = find_email_for(merge_request.assignee)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index 7a8c54673f7..f7ff8b80bd7 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -93,26 +93,27 @@ describe Projects::AutocompleteService do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
- let!(:group_milestone) { create(:milestone, group: group) }
- let!(:project_milestone) { create(:milestone, project: project) }
+ let!(:group_milestone1) { create(:milestone, group: group, due_date: '2017-01-01', title: 'Second Title') }
+ let!(:group_milestone2) { create(:milestone, group: group, due_date: '2017-01-01', title: 'First Title') }
+ let!(:project_milestone) { create(:milestone, project: project, due_date: '2016-01-01') }
let(:milestone_titles) { described_class.new(project, user).milestones.map(&:title) }
- it 'includes project and group milestones' do
- expect(milestone_titles).to eq([group_milestone.title, project_milestone.title])
+ it 'includes project and group milestones and sorts them correctly' do
+ expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title, group_milestone1.title])
end
it 'does not include closed milestones' do
- group_milestone.close
+ group_milestone1.close
- expect(milestone_titles).to eq([project_milestone.title])
+ expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title])
end
it 'does not include milestones from other projects in the group' do
other_project = create(:project, group: group)
project_milestone.update!(project: other_project)
- expect(milestone_titles).to eq([group_milestone.title])
+ expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title])
end
end
end
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
new file mode 100644
index 00000000000..bb0e274c93e
--- /dev/null
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Projects::GitlabProjectsImportService do
+ set(:namespace) { build(:namespace) }
+ let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+ subject { described_class.new(namespace.owner, { namespace_id: namespace.id, path: path, file: file }) }
+
+ describe '#execute' do
+ context 'with an invalid path' do
+ let(:path) { '/invalid-path/' }
+
+ it 'returns an invalid project' do
+ project = subject.execute
+
+ expect(project).not_to be_persisted
+ expect(project).not_to be_valid
+ end
+ end
+
+ context 'with a valid path' do
+ let(:path) { 'test-path' }
+
+ it 'creates a project' do
+ project = subject.execute
+
+ expect(project).to be_persisted
+ expect(project).to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index 46cd10cdc12..c40cd5b7548 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -105,12 +105,25 @@ describe SystemHooksService do
expect(data[:old_username]).to eq(user.username_was)
end
end
+
+ context 'user_failed_login' do
+ it 'contains state of user' do
+ user.ldap_block!
+
+ data = event_data(user, :failed_login)
+
+ expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :state)
+ expect(data[:username]).to eq(user.username)
+ expect(data[:state]).to eq('ldap_blocked')
+ end
+ end
end
context 'event names' do
it { expect(event_name(user, :create)).to eq "user_create" }
it { expect(event_name(user, :destroy)).to eq "user_destroy" }
it { expect(event_name(user, :rename)).to eq 'user_rename' }
+ it { expect(event_name(user, :failed_login)).to eq 'user_failed_login' }
it { expect(event_name(project, :create)).to eq "project_create" }
it { expect(event_name(project, :destroy)).to eq "project_destroy" }
it { expect(event_name(project, :rename)).to eq "project_rename" }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 965fd39c967..ab3a257f36f 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -158,7 +158,7 @@ describe SystemNoteService do
end
it 'builds a correct phrase when assignee removed' do
- expect(build_note([assignee1], [])).to eq 'removed assignee'
+ expect(build_note([assignee1], [])).to eq "unassigned @#{assignee1.username}"
end
it 'builds a correct phrase when assignees changed' do
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
index ff8b9595538..74d7715e50f 100644
--- a/spec/services/test_hooks/system_service_spec.rb
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -60,5 +60,25 @@ describe TestHooks::SystemService do
expect(service.execute).to include(success_result)
end
end
+
+ context 'merge_requests_events' do
+ let(:trigger) { 'merge_requests_events' }
+
+ it 'returns error message if the user does not have any repository with a merge request' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure one of your projects has merge requests.' })
+ end
+
+ it 'executes hook' do
+ trigger_key = :merge_request_hooks
+ sample_data = { data: 'sample' }
+ create(:project_member, user: current_user, project: project)
+ create(:merge_request, source_project: project)
+ allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index f51bb44086b..6186fb92bad 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -97,10 +97,6 @@ RSpec.configure do |config|
TestEnv.init
end
- config.after(:suite) do
- TestEnv.cleanup
- end
-
config.before(:example) do
# Skip pre-receive hook check so we can use the web editor and merge.
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
diff --git a/spec/support/devise_helpers.rb b/spec/support/devise_helpers.rb
index 890a2d9d287..66874e10f38 100644
--- a/spec/support/devise_helpers.rb
+++ b/spec/support/devise_helpers.rb
@@ -2,13 +2,16 @@ module DeviseHelpers
# explicitly tells Devise which mapping to use
# this is needed when we are testing a Devise controller bypassing the router
def set_devise_mapping(context:)
- env =
- if context.respond_to?(:env_config)
- context.env_config
- elsif context.respond_to?(:env)
- context.env
- end
+ env = env_from_context(context)
env['devise.mapping'] = Devise.mappings[:user] if env
end
+
+ def env_from_context(context)
+ if context.respond_to?(:env_config)
+ context.env_config
+ elsif context.respond_to?(:env)
+ context.env
+ end
+ end
end
diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb
index b39052923dd..1fb8252459f 100644
--- a/spec/support/email_helpers.rb
+++ b/spec/support/email_helpers.rb
@@ -30,4 +30,8 @@ module EmailHelpers
def email_recipients(kind: :to)
ActionMailer::Base.deliveries.flat_map(&kind)
end
+
+ def find_email_for(user)
+ ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) }
+ end
end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 50702a0ac88..b52b6a28c54 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -125,6 +125,13 @@ module LoginHelpers
})
end
+ def stub_omniauth_provider(provider, context: Rails.application)
+ env = env_from_context(context)
+
+ set_devise_mapping(context: context)
+ env['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
+ end
+
def stub_omniauth_saml_config(messages)
set_devise_mapping(context: Rails.application)
Rails.application.routes.disable_clear_and_finalize = true
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
index 3f1fd169b72..23f9b46ae0c 100644
--- a/spec/support/services_shared_context.rb
+++ b/spec/support/services_shared_context.rb
@@ -3,13 +3,9 @@ Service.available_services_names.each do |service|
let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym }
let(:service_klass) { "#{service}_service".classify.constantize }
- let(:service_fields) { service_klass.new.fields }
+ let(:service_instance) { service_klass.new }
+ let(:service_fields) { service_instance.fields }
let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
- let(:service_attrs_list_without_passwords) do
- service_fields
- .select { |field| field[:type] != 'password' }
- .map { |field| field[:name].to_sym}
- end
let(:service_attrs) do
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
new file mode 100644
index 00000000000..5b0b609f7f2
--- /dev/null
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -0,0 +1,99 @@
+RSpec.shared_examples 'a creatable merge request' do
+ include WaitForRequests
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:source_project) { target_project }
+ let!(:milestone) { create(:milestone, project: target_project) }
+ let!(:label) { create(:label, project: target_project) }
+ let!(:label2) { create(:label, project: target_project) }
+
+ before do
+ source_project.add_master(user)
+ target_project.add_master(user)
+ target_project.add_master(user2)
+
+ sign_in(user)
+ visit project_new_merge_request_path(
+ target_project,
+ merge_request: {
+ source_project_id: source_project.id,
+ target_project_id: target_project.id,
+ source_branch: 'fix',
+ target_branch: 'master'
+ })
+ end
+
+ it 'creates new merge request', :js do
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user2.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user2.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user2.name
+ end
+
+ click_link 'Assign to me'
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+
+ click_button 'Submit merge request'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+
+ it 'updates the branches when selecting a new target project' do
+ target_project_member = target_project.owner
+ CreateBranchService.new(target_project, target_project_member)
+ .execute('a-brand-new-branch-to-test', 'master')
+ visit project_new_merge_request_path(source_project)
+
+ first('.js-target-project').click
+ find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click
+
+ wait_for_requests
+
+ first('.js-target-branch').click
+
+ within('.dropdown-target-branch .dropdown-content') do
+ expect(page).to have_content('a-brand-new-branch-to-test')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
new file mode 100644
index 00000000000..645db41cddc
--- /dev/null
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -0,0 +1,140 @@
+RSpec.shared_examples 'an editable merge request' do
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:milestone) { create(:milestone, project: target_project) }
+ let!(:label) { create(:label, project: target_project) }
+ let!(:label2) { create(:label, project: target_project) }
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:source_project) { target_project }
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: source_project,
+ target_project: target_project,
+ source_branch: 'fix',
+ target_branch: 'master')
+ end
+
+ before do
+ source_project.add_master(user)
+ target_project.add_master(user)
+ target_project.add_master(user2)
+
+ sign_in(user)
+ visit edit_project_merge_request_path(target_project, merge_request)
+ end
+
+ it 'updates merge request', :js do
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+
+ click_button 'Save changes'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+
+ it 'description has autocomplete', :js do
+ find('#merge_request_description').native.send_keys('')
+ fill_in 'merge_request_description', with: '@'
+
+ expect(page).to have_selector('.atwho-view')
+ end
+
+ it 'has class js-quick-submit in form' do
+ expect(page).to have_selector('.js-quick-submit')
+ end
+
+ it 'warns about version conflict' do
+ merge_request.update(title: "New title")
+
+ fill_in 'merge_request_title', with: 'bug 345'
+ fill_in 'merge_request_description', with: 'bug description'
+
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Someone edited the merge request the same time you did'
+ end
+
+ it 'preserves description textarea height', :js do
+ long_description = %q(
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat.
+
+ Cras congue nec ligula tristique viverra. Curabitur fringilla fringilla fringilla. Donec rhoncus dignissim orci ut accumsan. Ut rutrum urna a rhoncus varius. Maecenas blandit, mauris nec accumsan gravida, augue nibh finibus magna, sed maximus turpis libero nec neque. Suspendisse at semper est. Nunc imperdiet dapibus dui, varius sollicitudin erat luctus non. Sed pellentesque ligula eget posuere facilisis. Donec dictum commodo volutpat. Donec egestas dui ac magna sollicitudin bibendum. Vivamus purus neque, ullamcorper ac feugiat et, tempus sit amet metus. Praesent quis viverra neque. Sed bibendum viverra est, eu aliquam mi ornare vitae. Proin et dapibus ipsum. Nunc tortor diam, malesuada nec interdum vel, placerat quis justo. Ut viverra at erat eu laoreet.
+
+ Pellentesque commodo, diam sit amet dignissim condimentum, tortor justo pretium est, non venenatis metus eros ut nunc. Etiam ut neque eget sem dapibus aliquam. Curabitur vel elit lorem. Nulla nec enim elit. Sed ut ex id justo facilisis convallis at ac augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam cursus egestas turpis non tristique. Suspendisse in erat sem. Fusce libero elit, fermentum gravida mauris id, auctor iaculis felis. Nullam vulputate tempor laoreet.
+
+ Nam tempor et magna sed convallis. Fusce sit amet sollicitudin risus, a ullamcorper lacus. Morbi gravida quis sem eget porttitor. Donec eu egestas mauris, in elementum tortor. Sed eget ex mi. Mauris iaculis tortor ut est auctor, nec dignissim quam sagittis. Suspendisse vel metus non quam suscipit tincidunt. Cras molestie lacus non justo finibus sodales quis vitae erat. In a porttitor nisi, id sollicitudin urna. Ut at felis tellus. Suspendisse potenti.
+
+ Maecenas leo ligula, varius at neque vitae, ornare maximus justo. Nullam convallis luctus risus et vulputate. Duis suscipit faucibus iaculis. Etiam quis tortor faucibus, tristique tellus sit amet, sodales neque. Nulla dapibus nisi vel aliquet consequat. Etiam faucibus, metus eget condimentum iaculis, enim urna lobortis sem, id efficitur eros sapien nec nisi. Aenean ut finibus ex.
+ )
+
+ fill_in 'merge_request_description', with: long_description
+
+ height = get_textarea_height
+ find('.js-md-preview-button').click
+ find('.js-md-write-button').click
+ new_height = get_textarea_height
+
+ expect(height).to eq(new_height)
+ end
+
+ context 'when "Remove source branch" is set' do
+ before do
+ merge_request.update!(merge_params: { 'force_remove_source_branch' => '1' })
+ end
+
+ it 'allows to unselect "Remove source branch"', :js do
+ expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
+
+ visit edit_project_merge_request_path(target_project, merge_request)
+ uncheck 'Remove source branch when merge request is accepted'
+
+ click_button 'Save changes'
+
+ expect(page).to have_unchecked_field 'remove-source-branch-input'
+ expect(page).to have_content 'Remove source branch'
+ end
+ end
+end
+
+def get_textarea_height
+ page.evaluate_script('document.getElementById("merge_request_description").offsetHeight')
+end
diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index 17f3a861ba8..e827a8da0b7 100644
--- a/spec/support/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -57,6 +57,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
@issue = issue_service.execute
@issues_sample_data = issue_service.hook_data(@issue, 'open')
+ project.add_developer(user)
opts = {
title: 'Awesome merge_request',
description: 'please fix',
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 25ff6094408..fd6368e7b40 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -90,10 +90,6 @@ module TestEnv
setup_forked_repo
end
- def cleanup
- stop_gitaly
- end
-
def disable_mailer
allow_any_instance_of(NotificationService).to receive(:mailer)
.and_return(double.as_null_object)
@@ -163,6 +159,8 @@ module TestEnv
spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s
@gitaly_pid = Bundler.with_original_env { IO.popen([spawn_script], &:read).to_i }
+ Kernel.at_exit { stop_gitaly }
+
wait_gitaly
end
@@ -309,7 +307,7 @@ module TestEnv
# Before we used Git clone's --mirror option, bare repos could end up
# with missing refs, clearing them and retrying should fix the issue.
- cleanup && clean_gitlab_test_path && init unless reset.call
+ clean_gitlab_test_path && init unless reset.call
end
end
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
index 4e15ccc534b..baa8ddb59e5 100644
--- a/spec/workers/new_issue_worker_spec.rb
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -44,8 +44,9 @@ describe NewIssueWorker do
expect { worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1)
end
- it 'creates a notification for the assignee' do
- expect(Notify).to receive(:new_issue_email).with(mentioned.id, issue.id).and_return(double(deliver_later: true))
+ it 'creates a notification for the mentioned user' do
+ expect(Notify).to receive(:new_issue_email).with(mentioned.id, issue.id, NotificationReason::MENTIONED)
+ .and_return(double(deliver_later: true))
worker.perform(issue.id, user.id)
end
diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb
index 9e0cbde45b1..c3f29a40d58 100644
--- a/spec/workers/new_merge_request_worker_spec.rb
+++ b/spec/workers/new_merge_request_worker_spec.rb
@@ -46,8 +46,10 @@ describe NewMergeRequestWorker do
expect { worker.perform(merge_request.id, user.id) }.to change { Event.count }.from(0).to(1)
end
- it 'creates a notification for the assignee' do
- expect(Notify).to receive(:new_merge_request_email).with(mentioned.id, merge_request.id).and_return(double(deliver_later: true))
+ it 'creates a notification for the mentioned user' do
+ expect(Notify).to receive(:new_merge_request_email)
+ .with(mentioned.id, merge_request.id, NotificationReason::MENTIONED)
+ .and_return(double(deliver_later: true))
worker.perform(merge_request.id, user.id)
end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 31598586f59..4912baa348c 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -47,6 +47,14 @@ describe RepositoryForkWorker do
perform!
end
+ it 'protects the default branch' do
+ expect_fork_repository.and_return(true)
+
+ perform!
+
+ expect(fork_project.protected_branches.first.name).to eq(fork_project.default_branch)
+ end
+
it 'flushes various caches' do
expect_fork_repository.and_return(true)
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 75de266369d..5ebad58e171 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -42,6 +42,7 @@ stages:
- build
- test
- review
+ - dast
- staging
- canary
- production
@@ -90,10 +91,14 @@ codequality:
performance:
stage: performance
- image:
- name: sitespeedio/sitespeed.io:6.0.3
- entrypoint: [""]
+ image: docker:latest
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:dind
script:
+ - setup_docker
- performance
artifacts:
paths:
@@ -112,7 +117,7 @@ sast:
- sast .
artifacts:
paths: [gl-sast-report.json]
-
+
sast:container:
image: docker:latest
variables:
@@ -126,6 +131,23 @@ sast:container:
artifacts:
paths: [gl-sast-container-report.json]
+dast:
+ stage: dast
+ allow_failure: true
+ image: owasp/zap2docker-stable
+ variables:
+ POSTGRES_DB: "false"
+ script:
+ - dast
+ artifacts:
+ paths: [gl-dast-report.json]
+ only:
+ refs:
+ - branches
+ kubernetes: active
+ except:
+ - master
+
review:
stage: review
script:
@@ -260,14 +282,14 @@ production:
export CI_APPLICATION_TAG=$CI_COMMIT_SHA
export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
-
+
function sast_container() {
docker run -d --name db arminc/clair-db:latest
docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1
apk add -U wget ca-certificates
docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- wget https://github.com/arminc/clair-scanner/releases/download/v6/clair-scanner_linux_386
- mv clair-scanner_linux_386 clair-scanner
+ wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+ mv clair-scanner_linux_amd64 clair-scanner
chmod +x clair-scanner
touch clair-whitelist.yml
./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
@@ -323,6 +345,12 @@ production:
replicas="$new_replicas"
fi
+ if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then
+ secret_name='gitlab-registry'
+ else
+ secret_name=''
+ fi
+
helm upgrade --install \
--wait \
--set service.enabled="$service_enabled" \
@@ -330,6 +358,7 @@ production:
--set image.repository="$CI_APPLICATION_REPOSITORY" \
--set image.tag="$CI_APPLICATION_TAG" \
--set image.pullPolicy=IfNotPresent \
+ --set image.secrets[0].name="$secret_name" \
--set application.track="$track" \
--set application.database_url="$DATABASE_URL" \
--set service.url="$CI_ENVIRONMENT_URL" \
@@ -458,6 +487,11 @@ production:
}
function create_secret() {
+ echo "Create secret..."
+ if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then
+ return
+ fi
+
kubectl create secret -n "$KUBE_NAMESPACE" \
docker-registry gitlab-registry \
--docker-server="$CI_REGISTRY" \
@@ -466,26 +500,34 @@ production:
--docker-email="$GITLAB_USER_EMAIL" \
-o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
}
-
+
+ function dast() {
+ export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
+
+ mkdir /zap/wrk/
+ /zap/zap-baseline.py -J gl-dast-report.json -t "$CI_ENVIRONMENT_URL" || true
+ cp /zap/wrk/gl-dast-report.json .
+ }
+
function performance() {
export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
-
+
mkdir gitlab-exporter
wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-3/index.js
-
+
mkdir sitespeed-results
-
+
if [ -f .gitlab-urls.txt ]
then
sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt
- /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt
+ docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.0.3 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt
else
- /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results $CI_ENVIRONMENT_URL
+ docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.0.3 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
fi
-
+
mv sitespeed-results/data/performance.json performance.json
}
-
+
function persist_environment_url() {
echo $CI_ENVIRONMENT_URL > environment_url.txt
}
diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml
index dd9496deb4d..5249449c7f8 100644
--- a/vendor/prometheus/values.yaml
+++ b/vendor/prometheus/values.yaml
@@ -1,134 +1,117 @@
-alertmanager: |
+alertmanager:
enabled: false
-kubeStateMetrics: |
- enabled: 'false'
+kubeStateMetrics:
+ enabled: false
-nodeExporter: |
- enabled: 'false'
+nodeExporter:
+ enabled: false
-pushgateway: |
- enabled: 'false'
+pushgateway:
+ enabled: false
-serverFiles: |
- alerts: ''
- rules: ''
+serverFiles:
+ alerts: ""
+ rules: ""
prometheus.yml: |-
- rule_files: |
+ rule_files:
- /etc/config/rules
- /etc/config/alerts
- scrape_configs: |
+ scrape_configs:
- job_name: prometheus
- static_configs: |
+ static_configs:
- targets:
- localhost:9090
-
- - job_name: 'kubernetes-apiservers'
- kubernetes_sd_configs: |
- - role: endpoints
+ - job_name: kubernetes-cadvisor
scheme: https
-
tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
insecure_skip_verify: true
- bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ kubernetes_sd_configs:
+ - role: node
+ api_server: https://kubernetes.default.svc:443
+ tls_config:
+ ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
relabel_configs:
- - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
- action: keep
- regex: default;kubernetes;https
- - job_name: 'kubernetes-nodes'
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: "(.+)"
+ target_label: __metrics_path__
+ replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
+ metric_relabel_configs:
+ - source_labels:
+ - pod_name
+ target_label: environment
+ regex: "(.+)-.+-.+"
+ - job_name: kubernetes-nodes
scheme: https
tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
insecure_skip_verify: true
- bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
kubernetes_sd_configs:
- - role: node
+ - role: node
+ api_server: https://kubernetes.default.svc:443
+ tls_config:
+ ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - target_label: __address__
- replacement: kubernetes.default.svc:443
- - source_labels: [__meta_kubernetes_node_name]
- regex: (.+)
- target_label: __metrics_path__
- replacement: /api/v1/nodes/${1}/proxy/metrics
-
- - job_name: 'kubernetes-service-endpoints'
- kubernetes_sd_configs:
- - role: endpoints
- relabel_configs: |
- - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
- action: keep
- regex: 'true'
- - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
- action: replace
- target_label: __scheme__
- regex: (https?)
- - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
- action: replace
- target_label: __metrics_path__
- regex: (.+)
- - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
- action: replace
- target_label: __address__
- regex: (.+)(?::\d+);(\d+)
- replacement: $1:$2
- - action: labelmap
- regex: __meta_kubernetes_service_label_(.+)
- - source_labels: [__meta_kubernetes_namespace]
- action: replace
- target_label: kubernetes_namespace
- - source_labels: [__meta_kubernetes_service_name]
- action: replace
- target_label: kubernetes_name
- - job_name: 'prometheus-pushgateway'
- honor_labels: true
- kubernetes_sd_configs: |
- - role: service
- relabel_configs: |
- - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
- action: keep
- regex: pushgateway
- - job_name: 'kubernetes-services'
- metrics_path: /probe
- params: |
- module: [http_2xx]
- kubernetes_sd_configs: |
- - role: service
- relabel_configs: |
- - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
- action: keep
- regex: 'true'
- - source_labels: [__address__]
- target_label: __param_target
- - target_label: __address__
- replacement: blackbox
- - source_labels: [__param_target]
- target_label: instance
- - action: labelmap
- regex: __meta_kubernetes_service_label_(.+)
- - source_labels: [__meta_kubernetes_namespace]
- target_label: kubernetes_namespace
- - source_labels: [__meta_kubernetes_service_name]
- target_label: kubernetes_name
- - job_name: 'kubernetes-pods'
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: "(.+)"
+ target_label: __metrics_path__
+ replacement: "/api/v1/nodes/${1}/proxy/metrics"
+ metric_relabel_configs:
+ - source_labels:
+ - pod_name
+ target_label: environment
+ regex: "(.+)-.+-.+"
+ - job_name: kubernetes-pods
+ tls_config:
+ ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ insecure_skip_verify: true
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
kubernetes_sd_configs:
- - role: pod
+ - role: pod
+ api_server: https://kubernetes.default.svc:443
+ tls_config:
+ ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
relabel_configs:
- - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
- action: keep
- regex: 'true'
- - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
- action: replace
- target_label: __metrics_path__
- regex: (.+)
- - action: labelmap
- regex: __meta_kubernetes_pod_label_(.+)
- - source_labels: [__meta_kubernetes_namespace]
- action: replace
- target_label: kubernetes_namespace
- - source_labels: [__meta_kubernetes_pod_name]
- action: replace
- target_label: kubernetes_pod_name
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_scrape
+ action: keep
+ regex: 'true'
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_path
+ action: replace
+ target_label: __metrics_path__
+ regex: "(.+)"
+ - source_labels:
+ - __address__
+ - __meta_kubernetes_pod_annotation_prometheus_io_port
+ action: replace
+ regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
+ replacement: "$1:$2"
+ target_label: __address__
+ - action: labelmap
+ regex: __meta_kubernetes_pod_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_pod_name
+ action: replace
+ target_label: kubernetes_pod_name
diff --git a/yarn.lock b/yarn.lock
index 5d40e833889..81add9b7247 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -62,6 +62,13 @@
version "2.0.48"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-2.0.48.tgz#3e90d8cde2d29015e5583017f7830cb3975b2eef"
+JSONStream@^1.0.3:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea"
+ dependencies:
+ jsonparse "^1.2.0"
+ through ">=2.2.7 <3"
+
abbrev@1, abbrev@1.0.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
@@ -101,10 +108,21 @@ acorn@^5.2.1:
version "5.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822"
+addressparser@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746"
+
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
+agent-base@2:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.1.1.tgz#d6de10d5af6132d5bd692427d46fc538539094c7"
+ dependencies:
+ extend "~3.0.0"
+ semver "~5.0.1"
+
ajv-keywords@^1.0.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
@@ -129,6 +147,15 @@ ajv@^5.0.0, ajv@^5.1.5:
json-schema-traverse "^0.3.0"
json-stable-stringify "^1.0.1"
+ajv@^5.1.0:
+ version "5.5.2"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
+ dependencies:
+ co "^4.6.0"
+ fast-deep-equal "^1.0.0"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.3.0"
+
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
@@ -161,6 +188,10 @@ ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -211,6 +242,10 @@ arr-flatten@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b"
+array-filter@~0.0.0:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
+
array-find-index@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
@@ -227,6 +262,14 @@ array-flatten@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
+array-map@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
+
+array-reduce@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
+
array-slice@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
@@ -237,7 +280,7 @@ array-union@^1.0.1:
dependencies:
array-uniq "^1.0.1"
-array-uniq@^1.0.1:
+array-uniq@^1.0.1, array-uniq@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
@@ -245,9 +288,9 @@ array-unique@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
-arraybuffer.slice@0.0.6:
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca"
+arraybuffer.slice@~0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
arrify@^1.0.0, arrify@^1.0.1:
version "1.0.1"
@@ -273,17 +316,31 @@ assert-plus@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
-assert@^1.1.1:
+assert@^1.1.1, assert@^1.4.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
dependencies:
util "0.10.3"
+ast-types@0.x.x:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd"
+
+astw@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/astw/-/astw-2.2.0.tgz#7bd41784d32493987aeb239b6b4e1c57a873b917"
+ dependencies:
+ acorn "^4.0.3"
+
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
-async@1.x, async@^1.4.0, async@^1.4.2, async@^1.5.2:
+async-limiter@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
+
+async@1.x, async@^1.4.0, async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -297,6 +354,12 @@ async@~0.9.0:
version "0.9.2"
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
+async@~2.1.2:
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
+ dependencies:
+ lodash "^4.14.0"
+
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -320,7 +383,11 @@ aws-sign2@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
-aws4@^1.2.1:
+aws-sign2@~0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+
+aws4@^1.2.1, aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
@@ -330,6 +397,12 @@ axios-mock-adapter@^1.10.0:
dependencies:
deep-equal "^1.0.1"
+axios@^0.15.3:
+ version "0.15.3"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.15.3.tgz#2c9d638b2e191a08ea1d6cc988eadd6ba5bdc053"
+ dependencies:
+ follow-redirects "1.0.0"
+
axios@^0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d"
@@ -984,6 +1057,12 @@ binary-extensions@^1.0.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0"
+bl@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-1.1.2.tgz#fdca871a99713aa00d19e3bbba41c44787a65398"
+ dependencies:
+ readable-stream "~2.0.5"
+
blackst0ne-mermaid@^7.1.0-fixed:
version "7.1.0-fixed"
resolved "https://registry.yarnpkg.com/blackst0ne-mermaid/-/blackst0ne-mermaid-7.1.0-fixed.tgz#3707b3a113d78610e3068e18a588f46b4688de49"
@@ -1049,6 +1128,18 @@ boom@2.x.x:
dependencies:
hoek "2.x.x"
+boom@4.x.x:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
+ dependencies:
+ hoek "4.x.x"
+
+boom@5.x.x:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
+ dependencies:
+ hoek "4.x.x"
+
bootstrap-sass@^3.3.6:
version "3.3.6"
resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.3.6.tgz#363b0d300e868d3e70134c1a742bb17288444fd1"
@@ -1078,6 +1169,22 @@ brorand@^1.0.1:
version "1.0.7"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.0.7.tgz#6677fa5e4901bdbf9c9ec2a748e28dca407a9bfc"
+browser-pack@^6.0.1:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.0.2.tgz#f86cd6cef4f5300c8e63e07a4d512f65fbff4531"
+ dependencies:
+ JSONStream "^1.0.3"
+ combine-source-map "~0.7.1"
+ defined "^1.0.0"
+ through2 "^2.0.0"
+ umd "^3.0.0"
+
+browser-resolve@^1.11.0, browser-resolve@^1.7.0:
+ version "1.11.2"
+ resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
+ dependencies:
+ resolve "1.1.7"
+
browserify-aes@^1.0.0, browserify-aes@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a"
@@ -1129,6 +1236,64 @@ browserify-zlib@^0.1.4:
dependencies:
pako "~0.2.0"
+browserify-zlib@~0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
+ dependencies:
+ pako "~1.0.5"
+
+browserify@^14.5.0:
+ version "14.5.0"
+ resolved "https://registry.yarnpkg.com/browserify/-/browserify-14.5.0.tgz#0bbbce521acd6e4d1d54d8e9365008efb85a9cc5"
+ dependencies:
+ JSONStream "^1.0.3"
+ assert "^1.4.0"
+ browser-pack "^6.0.1"
+ browser-resolve "^1.11.0"
+ browserify-zlib "~0.2.0"
+ buffer "^5.0.2"
+ cached-path-relative "^1.0.0"
+ concat-stream "~1.5.1"
+ console-browserify "^1.1.0"
+ constants-browserify "~1.0.0"
+ crypto-browserify "^3.0.0"
+ defined "^1.0.0"
+ deps-sort "^2.0.0"
+ domain-browser "~1.1.0"
+ duplexer2 "~0.1.2"
+ events "~1.1.0"
+ glob "^7.1.0"
+ has "^1.0.0"
+ htmlescape "^1.1.0"
+ https-browserify "^1.0.0"
+ inherits "~2.0.1"
+ insert-module-globals "^7.0.0"
+ labeled-stream-splicer "^2.0.0"
+ module-deps "^4.0.8"
+ os-browserify "~0.3.0"
+ parents "^1.0.1"
+ path-browserify "~0.0.0"
+ process "~0.11.0"
+ punycode "^1.3.2"
+ querystring-es3 "~0.2.0"
+ read-only-stream "^2.0.0"
+ readable-stream "^2.0.2"
+ resolve "^1.1.4"
+ shasum "^1.0.0"
+ shell-quote "^1.6.1"
+ stream-browserify "^2.0.0"
+ stream-http "^2.0.0"
+ string_decoder "~1.0.0"
+ subarg "^1.0.0"
+ syntax-error "^1.1.1"
+ through2 "^2.0.0"
+ timers-browserify "^1.0.1"
+ tty-browserify "~0.0.0"
+ url "~0.11.0"
+ util "~0.10.1"
+ vm-browserify "~0.0.1"
+ xtend "^4.0.0"
+
browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
version "1.7.7"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
@@ -1152,6 +1317,25 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
+buffer@^5.0.2:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.8.tgz#84daa52e7cf2fa8ce4195bc5cf0f7809e0930b24"
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
+
+buildmail@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/buildmail/-/buildmail-4.0.1.tgz#877f7738b78729871c9a105e3b837d2be11a7a72"
+ dependencies:
+ addressparser "1.0.1"
+ libbase64 "0.1.0"
+ libmime "3.0.0"
+ libqp "1.1.0"
+ nodemailer-fetch "1.6.0"
+ nodemailer-shared "1.1.0"
+ punycode "1.4.1"
+
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@@ -1168,6 +1352,14 @@ bytes@2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a"
+bytes@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+
+cached-path-relative@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
+
caller-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@@ -1218,6 +1410,10 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000649"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000649.tgz#1ee1754a6df235450c8b7cd15e0ebf507221a86a"
+caseless@~0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
+
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -1239,7 +1435,7 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
-chalk@^2.0.0, chalk@^2.3.0:
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
dependencies:
@@ -1272,6 +1468,10 @@ circular-json@^0.3.1:
version "0.3.3"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
+circular-json@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.4.0.tgz#c448ea998b7fe31ecf472ec29c6b608e2e2a62fd"
+
clap@^1.0.9:
version "1.1.3"
resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.3.tgz#b3bd36e93dd4cbfb395a3c26896352445265c05b"
@@ -1324,6 +1524,10 @@ co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+co@~3.0.6:
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/co/-/co-3.0.6.tgz#1445f226c5eb956138e68c9ac30167ea7d2e6bda"
+
coa@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.1.tgz#7f959346cfc8719e3f7233cd6852854a7c67d8a3"
@@ -1376,6 +1580,15 @@ combine-lists@^1.0.0:
dependencies:
lodash "^4.5.0"
+combine-source-map@~0.7.1:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e"
+ dependencies:
+ convert-source-map "~1.1.0"
+ inline-source-map "~0.6.0"
+ lodash.memoize "~3.0.3"
+ source-map "~0.5.3"
+
combined-stream@^1.0.5, combined-stream@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
@@ -1396,10 +1609,6 @@ component-bind@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
-component-emitter@1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3"
-
component-emitter@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
@@ -1445,6 +1654,14 @@ concat-stream@^1.5.2:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+concat-stream@~1.5.0, concat-stream@~1.5.1:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "~2.0.0"
+ typedarray "~0.0.5"
+
configstore@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021"
@@ -1487,7 +1704,7 @@ consolidate@^0.14.0:
dependencies:
bluebird "^3.1.1"
-constants-browserify@^1.0.0:
+constants-browserify@^1.0.0, constants-browserify@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@@ -1507,6 +1724,10 @@ convert-source-map@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
+convert-source-map@~1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860"
+
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -1598,6 +1819,28 @@ cryptiles@2.x.x:
dependencies:
boom "2.x.x"
+cryptiles@3.x.x:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
+ dependencies:
+ boom "5.x.x"
+
+crypto-browserify@^3.0.0:
+ version "3.12.0"
+ resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
+ dependencies:
+ browserify-cipher "^1.0.0"
+ browserify-sign "^4.0.0"
+ create-ecdh "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.0"
+ diffie-hellman "^5.0.0"
+ inherits "^2.0.1"
+ pbkdf2 "^3.0.3"
+ public-encrypt "^4.0.0"
+ randombytes "^2.0.0"
+ randomfill "^1.0.3"
+
crypto-browserify@^3.11.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522"
@@ -1848,6 +2091,14 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
+data-uri-to-buffer@1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835"
+
+date-format@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/date-format/-/date-format-1.2.0.tgz#615e828e233dd1ab9bb9ae0950e0ceccfa6ecad8"
+
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -1856,18 +2107,18 @@ de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
-debug@2.2.0:
+debug@2, debug@~2.6.4, debug@~2.6.6, debug@~2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ dependencies:
+ ms "2.0.0"
+
+debug@2.2.0, debug@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
dependencies:
ms "0.7.1"
-debug@2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c"
- dependencies:
- ms "0.7.2"
-
debug@2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e"
@@ -1929,6 +2180,14 @@ defined@^1.0.0, defined@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+degenerator@~1.0.2:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095"
+ dependencies:
+ ast-types "0.x.x"
+ escodegen "1.x.x"
+ esprima "3.x.x"
+
del@^2.0.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
@@ -1972,6 +2231,15 @@ depd@1.1.1, depd@~1.1.0, depd@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
+deps-sort@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.0.tgz#091724902e84658260eb910748cccd1af6e21fb5"
+ dependencies:
+ JSONStream "^1.0.3"
+ shasum "^1.0.0"
+ subarg "^1.0.0"
+ through2 "^2.0.0"
+
des.js@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
@@ -1993,6 +2261,13 @@ detect-node@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127"
+detective@^4.0.0:
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e"
+ dependencies:
+ acorn "^5.2.1"
+ defined "^1.0.0"
+
di@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
@@ -2060,7 +2335,7 @@ dom-serializer@0:
domelementtype "~1.1.1"
entities "~1.1.1"
-domain-browser@^1.1.1:
+domain-browser@^1.1.1, domain-browser@~1.1.0:
version "1.1.7"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
@@ -2085,10 +2360,20 @@ domutils@^1.5.1:
dom-serializer "0"
domelementtype "1"
+double-ended-queue@^2.1.0-0:
+ version "2.1.0-0"
+ resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
+
dropzone@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3"
+duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
+ dependencies:
+ readable-stream "^2.0.2"
+
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@@ -2151,44 +2436,44 @@ end-of-stream@^1.0.0:
dependencies:
once "^1.4.0"
-engine.io-client@1.8.3:
- version "1.8.3"
- resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.3.tgz#1798ed93451246453d4c6f635d7a201fe940d5ab"
+engine.io-client@~3.1.0:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.1.4.tgz#4fcf1370b47163bd2ce9be2733972430350d4ea1"
dependencies:
component-emitter "1.2.1"
component-inherit "0.0.3"
- debug "2.3.3"
- engine.io-parser "1.3.2"
+ debug "~2.6.9"
+ engine.io-parser "~2.1.1"
has-cors "1.1.0"
indexof "0.0.1"
- parsejson "0.0.3"
parseqs "0.0.5"
parseuri "0.0.5"
- ws "1.1.2"
- xmlhttprequest-ssl "1.5.3"
+ ws "~3.3.1"
+ xmlhttprequest-ssl "~1.5.4"
yeast "0.1.2"
-engine.io-parser@1.3.2:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a"
+engine.io-parser@~2.1.0, engine.io-parser@~2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.2.tgz#4c0f4cff79aaeecbbdcfdea66a823c6085409196"
dependencies:
after "0.8.2"
- arraybuffer.slice "0.0.6"
+ arraybuffer.slice "~0.0.7"
base64-arraybuffer "0.1.5"
blob "0.0.4"
- has-binary "0.1.7"
- wtf-8 "1.0.0"
+ has-binary2 "~1.0.2"
-engine.io@1.8.3:
- version "1.8.3"
- resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.3.tgz#8de7f97895d20d39b85f88eeee777b2bd42b13d4"
+engine.io@~3.1.0:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.1.4.tgz#3d0211b70a552ce841ffc7da8627b301a9a4162e"
dependencies:
accepts "1.3.3"
base64id "1.0.0"
cookie "0.3.1"
- debug "2.3.3"
- engine.io-parser "1.3.2"
- ws "1.1.2"
+ debug "~2.6.9"
+ engine.io-parser "~2.1.0"
+ ws "~3.3.1"
+ optionalDependencies:
+ uws "~0.14.4"
enhanced-resolve@^3.4.0:
version "3.4.1"
@@ -2320,6 +2605,17 @@ escodegen@1.8.x:
optionalDependencies:
source-map "~0.2.0"
+escodegen@1.x.x:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852"
+ dependencies:
+ esprima "^3.1.3"
+ estraverse "^4.2.0"
+ esutils "^2.0.2"
+ optionator "^0.8.1"
+ optionalDependencies:
+ source-map "~0.5.6"
+
escope@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
@@ -2477,6 +2773,10 @@ esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+esprima@3.x.x, esprima@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+
esprima@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
@@ -2541,7 +2841,7 @@ eventemitter3@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
-events@^1.0.0:
+events@^1.0.0, events@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
@@ -2640,7 +2940,7 @@ express@^4.13.3, express@^4.15.2:
utils-merge "1.0.0"
vary "~1.1.1"
-extend@^3.0.0, extend@~3.0.0:
+extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
@@ -2658,6 +2958,10 @@ fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
+fast-json-stable-stringify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+
fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
@@ -2704,6 +3008,10 @@ file-loader@^0.11.1:
dependencies:
loader-utils "^1.0.2"
+file-uri-to-path@1:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
+
filename-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
@@ -2783,6 +3091,12 @@ flatten@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
+follow-redirects@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.0.0.tgz#8e34298cbd2e176f254effec75a1c78cc849fd37"
+ dependencies:
+ debug "^2.2.0"
+
follow-redirects@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.6.tgz#4dcdc7e4ab3dd6765a97ff89c3b4c258117c79bf"
@@ -2813,6 +3127,14 @@ forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+form-data@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.0.0.tgz#6f0aebadcc5da16c13e1ecc11137d85f9b883b25"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.11"
+
form-data@~2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
@@ -2821,6 +3143,14 @@ form-data@~2.1.1:
combined-stream "^1.0.5"
mime-types "^2.1.12"
+form-data@~2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.12"
+
forwarded@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
@@ -2877,6 +3207,13 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
mkdirp ">=0.5 0"
rimraf "2"
+ftp@~0.3.10:
+ version "0.3.10"
+ resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
+ dependencies:
+ readable-stream "1.1.x"
+ xregexp "2.0.0"
+
function-bind@^1.0.2, function-bind@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
@@ -2924,6 +3261,17 @@ get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
+get-uri@2:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.1.tgz#dbdcacacd8c608a38316869368117697a1631c59"
+ dependencies:
+ data-uri-to-buffer "1"
+ debug "2"
+ extend "3"
+ file-uri-to-path "1"
+ ftp "~0.3.10"
+ readable-stream "2"
+
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
@@ -2974,7 +3322,7 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@~7.1.2:
+glob@^7.1.0, glob@~7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
dependencies:
@@ -3092,6 +3440,19 @@ har-schema@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
+har-schema@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+
+har-validator@~2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
+ dependencies:
+ chalk "^1.1.1"
+ commander "^2.9.0"
+ is-my-json-valid "^2.12.4"
+ pinkie-promise "^2.0.0"
+
har-validator@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
@@ -3099,17 +3460,24 @@ har-validator@~4.2.1:
ajv "^4.9.1"
har-schema "^1.0.5"
+har-validator@~5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
+ dependencies:
+ ajv "^5.1.0"
+ har-schema "^2.0.0"
+
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
dependencies:
ansi-regex "^2.0.0"
-has-binary@0.1.7:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c"
+has-binary2@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.2.tgz#e83dba49f0b9be4d026d27365350d9f03f54be98"
dependencies:
- isarray "0.0.1"
+ isarray "2.0.1"
has-cors@1.1.0:
version "1.1.0"
@@ -3137,7 +3505,7 @@ has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
-has@^1.0.1, has@~1.0.1:
+has@^1.0.0, has@^1.0.1, has@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
dependencies:
@@ -3162,14 +3530,34 @@ hawk@~3.1.3:
hoek "2.x.x"
sntp "1.x.x"
+hawk@~6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
+ dependencies:
+ boom "4.x.x"
+ cryptiles "3.x.x"
+ hoek "4.x.x"
+ sntp "2.x.x"
+
he@^1.1.0, he@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
+hipchat-notifier@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/hipchat-notifier/-/hipchat-notifier-1.1.0.tgz#b6d249755437c191082367799d3ba9a0f23b231e"
+ dependencies:
+ lodash "^4.0.0"
+ request "^2.0.0"
+
hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+hoek@4.x.x:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
+
home-or-tmp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -3198,7 +3586,11 @@ html-entities@1.2.0, html-entities@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2"
-htmlparser2@^3.8.2:
+htmlescape@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
+
+htmlparser2@^3.8.2, htmlparser2@^3.9.0:
version "3.9.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
dependencies:
@@ -3213,6 +3605,15 @@ http-deceiver@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
+http-errors@1.6.2, http-errors@~1.6.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
+ dependencies:
+ depd "1.1.1"
+ inherits "2.0.3"
+ setprototypeof "1.0.3"
+ statuses ">= 1.3.1 < 2"
+
http-errors@~1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257"
@@ -3222,14 +3623,13 @@ http-errors@~1.6.1:
setprototypeof "1.0.3"
statuses ">= 1.3.1 < 2"
-http-errors@~1.6.2:
- version "1.6.2"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
+http-proxy-agent@1:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz#cc1ce38e453bf984a0f7702d2dd59c73d081284a"
dependencies:
- depd "1.1.1"
- inherits "2.0.3"
- setprototypeof "1.0.3"
- statuses ">= 1.3.1 < 2"
+ agent-base "2"
+ debug "2"
+ extend "3"
http-proxy-middleware@~0.17.4:
version "0.17.4"
@@ -3255,14 +3655,49 @@ http-signature@~1.1.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
+http-signature@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+ dependencies:
+ assert-plus "^1.0.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+httpntlm@1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/httpntlm/-/httpntlm-1.6.1.tgz#ad01527143a2e8773cfae6a96f58656bb52a34b2"
+ dependencies:
+ httpreq ">=0.4.22"
+ underscore "~1.7.0"
+
+httpreq@>=0.4.22:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/httpreq/-/httpreq-0.4.24.tgz#4335ffd82cd969668a39465c929ac61d6393627f"
+
https-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
+https-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
+
+https-proxy-agent@1:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6"
+ dependencies:
+ agent-base "2"
+ debug "2"
+ extend "3"
+
iconv-lite@0.4.15:
version "0.4.15"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
+iconv-lite@0.4.19:
+ version "0.4.19"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+
icss-replace-symbols@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5"
@@ -3312,6 +3747,14 @@ infinity-agent@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/infinity-agent/-/infinity-agent-2.0.3.tgz#45e0e2ff7a9eb030b27d62b74b3744b7a7ac4216"
+inflection@~1.10.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.10.0.tgz#5bffcb1197ad3e81050f8e17e21668087ee9eb2f"
+
+inflection@~1.3.0:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.3.8.tgz#cbd160da9f75b14c3cc63578d4f396784bf3014e"
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -3331,6 +3774,12 @@ ini@~1.3.0:
version "1.3.4"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
+inline-source-map@~0.6.0:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5"
+ dependencies:
+ source-map "~0.5.3"
+
inquirer@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
@@ -3349,6 +3798,19 @@ inquirer@^0.12.0:
strip-ansi "^3.0.0"
through "^2.3.6"
+insert-module-globals@^7.0.0:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.1.tgz#c03bf4e01cb086d5b5e5ace8ad0afe7889d638c3"
+ dependencies:
+ JSONStream "^1.0.3"
+ combine-source-map "~0.7.1"
+ concat-stream "~1.5.1"
+ is-buffer "^1.1.0"
+ lexical-scope "^1.2.0"
+ process "~0.11.0"
+ through2 "^2.0.0"
+ xtend "^4.0.0"
+
internal-ip@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"
@@ -3369,7 +3831,11 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-ip@^1.1.0, ip@^1.1.5:
+ip@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ip/-/ip-1.0.1.tgz#c7e356cdea225ae71b36d70f2e71a92ba4e42590"
+
+ip@^1.1.0, ip@^1.1.2, ip@^1.1.4, ip@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
@@ -3402,6 +3868,10 @@ is-buffer@^1.0.2, is-buffer@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
+is-buffer@^1.1.0:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+
is-builtin-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
@@ -3479,6 +3949,15 @@ is-my-json-valid@^2.10.0:
jsonpointer "^4.0.0"
xtend "^4.0.0"
+is-my-json-valid@^2.12.4:
+ version "2.17.1"
+ resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471"
+ dependencies:
+ generate-function "^2.0.0"
+ generate-object-property "^1.1.0"
+ jsonpointer "^4.0.0"
+ xtend "^4.0.0"
+
is-npm@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
@@ -3587,7 +4066,7 @@ is-windows@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
-isarray@0.0.1:
+isarray@0.0.1, isarray@~0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -3595,6 +4074,10 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+isarray@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
+
isbinaryfile@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621"
@@ -3613,33 +4096,33 @@ isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
-istanbul-api@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.1.tgz#d36e2f1560d1a43ce304c4ff7338182de61c8f73"
+istanbul-api@^1.1.14:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620"
dependencies:
async "^2.1.4"
fileset "^2.0.2"
- istanbul-lib-coverage "^1.0.0"
- istanbul-lib-hook "^1.0.0"
- istanbul-lib-instrument "^1.3.0"
- istanbul-lib-report "^1.0.0-alpha.3"
- istanbul-lib-source-maps "^1.1.0"
- istanbul-reports "^1.0.0"
+ istanbul-lib-coverage "^1.1.1"
+ istanbul-lib-hook "^1.1.0"
+ istanbul-lib-instrument "^1.9.1"
+ istanbul-lib-report "^1.1.2"
+ istanbul-lib-source-maps "^1.2.2"
+ istanbul-reports "^1.1.3"
js-yaml "^3.7.0"
mkdirp "^0.5.1"
once "^1.4.0"
-istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.0.0-alpha, istanbul-lib-coverage@^1.0.0-alpha.0, istanbul-lib-coverage@^1.1.1:
+istanbul-lib-coverage@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da"
-istanbul-lib-hook@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.0.tgz#fc5367ee27f59268e8f060b0c7aaf051d9c425c5"
+istanbul-lib-hook@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b"
dependencies:
append-transform "^0.4.0"
-istanbul-lib-instrument@^1.3.0, istanbul-lib-instrument@^1.7.5:
+istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e"
dependencies:
@@ -3651,29 +4134,28 @@ istanbul-lib-instrument@^1.3.0, istanbul-lib-instrument@^1.7.5:
istanbul-lib-coverage "^1.1.1"
semver "^5.3.0"
-istanbul-lib-report@^1.0.0-alpha.3:
- version "1.0.0-alpha.3"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.0.0-alpha.3.tgz#32d5f6ec7f33ca3a602209e278b2e6ff143498af"
+istanbul-lib-report@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425"
dependencies:
- async "^1.4.2"
- istanbul-lib-coverage "^1.0.0-alpha"
+ istanbul-lib-coverage "^1.1.1"
mkdirp "^0.5.1"
path-parse "^1.0.5"
- rimraf "^2.4.3"
supports-color "^3.1.2"
-istanbul-lib-source-maps@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.1.0.tgz#9d429218f35b823560ea300a96ff0c3bbdab785f"
+istanbul-lib-source-maps@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c"
dependencies:
- istanbul-lib-coverage "^1.0.0-alpha.0"
+ debug "^3.1.0"
+ istanbul-lib-coverage "^1.1.1"
mkdirp "^0.5.1"
- rimraf "^2.4.4"
+ rimraf "^2.6.1"
source-map "^0.5.3"
-istanbul-reports@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.0.1.tgz#9a17176bc4a6cbebdae52b2f15961d52fa623fbc"
+istanbul-reports@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10"
dependencies:
handlebars "^4.0.3"
@@ -3781,11 +4263,17 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
dependencies:
jsonify "~0.0.0"
-json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
+json-stable-stringify@~0.0.0:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45"
+ dependencies:
+ jsonify "~0.0.0"
+
+json-stringify-safe@5.0.x, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
-json3@3.3.2, json3@^3.3.2:
+json3@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
@@ -3803,6 +4291,10 @@ jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+jsonparse@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
+
jsonpointer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
@@ -3830,28 +4322,31 @@ jszip@^3.1.3:
pako "~1.0.2"
readable-stream "~2.0.6"
-karma-chrome-launcher@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf"
+karma-chrome-launcher@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf"
dependencies:
fs-access "^1.0.0"
which "^1.2.1"
-karma-coverage-istanbul-reporter@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-0.2.0.tgz#5766263338adeb0026f7e4ac7a89a5f056c5642c"
+karma-coverage-istanbul-reporter@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.3.3.tgz#daf26051d5a0daa5838a4ce81aa4a41724bdf36b"
dependencies:
- istanbul-api "^1.1.1"
+ istanbul-api "^1.1.14"
+ minimatch "^3.0.4"
-karma-jasmine@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.0.tgz#22e4c06bf9a182e5294d1f705e3733811b810acf"
+karma-jasmine@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.1.tgz#6fe840e75a11600c9d91e84b33c458e1c46a3529"
-karma-mocha-reporter@^2.2.2:
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/karma-mocha-reporter/-/karma-mocha-reporter-2.2.2.tgz#876de9a287244e54a608591732a98e66611f6abe"
+karma-mocha-reporter@^2.2.5:
+ version "2.2.5"
+ resolved "https://registry.yarnpkg.com/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz#15120095e8ed819186e47a0b012f3cd741895560"
dependencies:
- chalk "1.1.3"
+ chalk "^2.1.0"
+ log-symbols "^2.1.0"
+ strip-ansi "^4.0.0"
karma-sourcemap-loader@^0.3.7:
version "0.3.7"
@@ -3859,22 +4354,23 @@ karma-sourcemap-loader@^0.3.7:
dependencies:
graceful-fs "^4.1.2"
-karma-webpack@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.4.tgz#3e2d4f48ba94a878e1c66bb8e1ae6128987a175b"
+karma-webpack@2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.7.tgz#dc3a492b478f10e8e3ccb9f58171b623f7070a1f"
dependencies:
async "~0.9.0"
loader-utils "^0.2.5"
lodash "^3.8.0"
- source-map "^0.1.41"
- webpack-dev-middleware "^1.0.11"
+ source-map "^0.5.6"
+ webpack-dev-middleware "^1.12.0"
-karma@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/karma/-/karma-1.7.0.tgz#6f7a1a406446fa2e187ec95398698f4cee476269"
+karma@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-2.0.0.tgz#a02698dd7f0f05ff5eb66ab8f65582490b512e58"
dependencies:
bluebird "^3.3.0"
body-parser "^1.16.1"
+ browserify "^14.5.0"
chokidar "^1.4.1"
colors "^1.1.0"
combine-lists "^1.0.0"
@@ -3887,8 +4383,8 @@ karma@^1.7.0:
graceful-fs "^4.1.2"
http-proxy "^1.13.0"
isbinaryfile "^3.0.0"
- lodash "^3.8.0"
- log4js "^0.6.31"
+ lodash "^4.17.4"
+ log4js "^2.3.9"
mime "^1.3.4"
minimatch "^3.0.2"
optimist "^0.6.1"
@@ -3896,9 +4392,9 @@ karma@^1.7.0:
range-parser "^1.2.0"
rimraf "^2.6.0"
safe-buffer "^5.0.1"
- socket.io "1.7.3"
- source-map "^0.5.3"
- tmp "0.0.31"
+ socket.io "2.0.4"
+ source-map "^0.6.1"
+ tmp "0.0.33"
useragent "^2.1.12"
kind-of@^3.0.2:
@@ -3913,6 +4409,14 @@ klaw@^1.0.0:
optionalDependencies:
graceful-fs "^4.1.9"
+labeled-stream-splicer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz#a52e1d138024c00b86b1c0c91f677918b8ae0a59"
+ dependencies:
+ inherits "^2.0.1"
+ isarray "~0.0.1"
+ stream-splicer "^2.0.0"
+
latest-version@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-1.0.1.tgz#72cfc46e3e8d1be651e1ebb54ea9f6ea96f374bb"
@@ -3936,6 +4440,28 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
+lexical-scope@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/lexical-scope/-/lexical-scope-1.2.0.tgz#fcea5edc704a4b3a8796cdca419c3a0afaf22df4"
+ dependencies:
+ astw "^2.0.0"
+
+libbase64@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/libbase64/-/libbase64-0.1.0.tgz#62351a839563ac5ff5bd26f12f60e9830bb751e6"
+
+libmime@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/libmime/-/libmime-3.0.0.tgz#51a1a9e7448ecbd32cda54421675bb21bc093da6"
+ dependencies:
+ iconv-lite "0.4.15"
+ libbase64 "0.1.0"
+ libqp "1.1.0"
+
+libqp@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/libqp/-/libqp-1.1.0.tgz#f5e6e06ad74b794fb5b5b66988bf728ef1dedbe8"
+
lie@~3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
@@ -4054,6 +4580,10 @@ lodash.capitalize@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
+lodash.clonedeep@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+
lodash.cond@^4.3.0:
version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
@@ -4069,6 +4599,10 @@ lodash.defaults@^3.1.2:
lodash.assign "^3.0.0"
lodash.restparam "^3.0.0"
+lodash.escaperegexp@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
+
lodash.get@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-3.7.0.tgz#3ce68ae2c91683b281cc5394128303cbf75e691f"
@@ -4103,6 +4637,14 @@ lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+lodash.memoize@~3.0.3:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
+
+lodash.mergewith@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55"
+
lodash.restparam@^3.0.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
@@ -4122,7 +4664,7 @@ lodash.words@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-4.2.0.tgz#5ecfeaf8ecf8acaa8e0c8386295f1993c9cf4036"
-lodash@4.17.4, lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0:
+lodash@4.17.4, lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -4130,12 +4672,37 @@ lodash@^3.8.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
-log4js@^0.6.31:
- version "0.6.38"
- resolved "https://registry.yarnpkg.com/log4js/-/log4js-0.6.38.tgz#2c494116695d6fb25480943d3fc872e662a522fd"
+log-symbols@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.1.0.tgz#f35fa60e278832b538dc4dddcbb478a45d3e3be6"
dependencies:
- readable-stream "~1.0.2"
- semver "~4.3.3"
+ chalk "^2.0.1"
+
+log4js@^2.3.9:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/log4js/-/log4js-2.4.1.tgz#b0c4e88133e0e3056afdc6f91f7f377576158778"
+ dependencies:
+ circular-json "^0.4.0"
+ date-format "^1.2.0"
+ debug "^3.1.0"
+ semver "^5.3.0"
+ streamroller "^0.7.0"
+ optionalDependencies:
+ axios "^0.15.3"
+ hipchat-notifier "^1.1.0"
+ loggly "^1.1.0"
+ mailgun-js "^0.7.0"
+ nodemailer "^2.5.0"
+ redis "^2.7.1"
+ slack-node "~0.2.0"
+
+loggly@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/loggly/-/loggly-1.1.1.tgz#0a0fc1d3fa3a5ec44fdc7b897beba2a4695cebee"
+ dependencies:
+ json-stringify-safe "5.0.x"
+ request "2.75.x"
+ timespan "2.3.x"
loglevel@^1.4.1:
version "1.4.1"
@@ -4173,10 +4740,35 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
+lru-cache@~2.6.5:
+ version "2.6.5"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"
+
macaddress@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
+mailcomposer@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/mailcomposer/-/mailcomposer-4.0.1.tgz#0e1c44b2a07cf740ee17dc149ba009f19cadfeb4"
+ dependencies:
+ buildmail "4.0.1"
+ libmime "3.0.0"
+
+mailgun-js@^0.7.0:
+ version "0.7.15"
+ resolved "https://registry.yarnpkg.com/mailgun-js/-/mailgun-js-0.7.15.tgz#ee366a20dac64c3c15c03d6c1b3e0ed795252abb"
+ dependencies:
+ async "~2.1.2"
+ debug "~2.2.0"
+ form-data "~2.1.1"
+ inflection "~1.10.0"
+ is-stream "^1.1.0"
+ path-proxy "~1.0.0"
+ proxy-agent "~2.0.0"
+ q "~1.4.0"
+ tsscmp "~1.0.0"
+
make-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978"
@@ -4191,9 +4783,9 @@ map-stream@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
-marked@^0.3.6:
- version "0.3.6"
- resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7"
+marked@^0.3.12:
+ version "0.3.12"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519"
math-expression-evaluator@^1.2.14:
version "1.2.16"
@@ -4276,6 +4868,16 @@ mime-db@~1.27.0:
version "1.27.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
+mime-db@~1.30.0:
+ version "1.30.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
+
+mime-types@^2.1.11, mime-types@~2.1.17:
+ version "2.1.17"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
+ dependencies:
+ mime-db "~1.30.0"
+
mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7:
version "2.1.15"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
@@ -4286,6 +4888,10 @@ mime@1.3.4, mime@1.3.x, mime@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
+mime@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+
mimic-fn@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
@@ -4314,7 +4920,7 @@ minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0:
+minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -4324,6 +4930,26 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd
dependencies:
minimist "0.0.8"
+module-deps@^4.0.8:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-4.1.1.tgz#23215833f1da13fd606ccb8087b44852dcb821fd"
+ dependencies:
+ JSONStream "^1.0.3"
+ browser-resolve "^1.7.0"
+ cached-path-relative "^1.0.0"
+ concat-stream "~1.5.0"
+ defined "^1.0.0"
+ detective "^4.0.0"
+ duplexer2 "^0.1.2"
+ inherits "^2.0.1"
+ parents "^1.0.0"
+ readable-stream "^2.0.2"
+ resolve "^1.1.3"
+ stream-combiner2 "^1.1.1"
+ subarg "^1.0.0"
+ through2 "^2.0.0"
+ xtend "^4.0.0"
+
moment@2.x, moment@^2.18.1:
version "2.19.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe"
@@ -4340,10 +4966,6 @@ ms@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
-ms@0.7.2:
- version "0.7.2"
- resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
-
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -4385,6 +5007,10 @@ nested-error-stacks@^1.0.0:
dependencies:
inherits "~2.0.1"
+netmask@~1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
+
node-dir@^0.1.10:
version "0.1.17"
resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"
@@ -4466,6 +5092,59 @@ node-pre-gyp@^0.6.36:
tar "^2.2.1"
tar-pack "^3.4.0"
+node-uuid@~1.4.7:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907"
+
+nodemailer-direct-transport@3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz#e96fafb90358560947e569017d97e60738a50a86"
+ dependencies:
+ nodemailer-shared "1.1.0"
+ smtp-connection "2.12.0"
+
+nodemailer-fetch@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz#79c4908a1c0f5f375b73fe888da9828f6dc963a4"
+
+nodemailer-shared@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz#cf5994e2fd268d00f5cf0fa767a08169edb07ec0"
+ dependencies:
+ nodemailer-fetch "1.6.0"
+
+nodemailer-smtp-pool@2.8.2:
+ version "2.8.2"
+ resolved "https://registry.yarnpkg.com/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz#2eb94d6cf85780b1b4725ce853b9cbd5e8da8c72"
+ dependencies:
+ nodemailer-shared "1.1.0"
+ nodemailer-wellknown "0.1.10"
+ smtp-connection "2.12.0"
+
+nodemailer-smtp-transport@2.7.2:
+ version "2.7.2"
+ resolved "https://registry.yarnpkg.com/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz#03d71c76314f14ac7dbc7bf033a6a6d16d67fb77"
+ dependencies:
+ nodemailer-shared "1.1.0"
+ nodemailer-wellknown "0.1.10"
+ smtp-connection "2.12.0"
+
+nodemailer-wellknown@0.1.10:
+ version "0.1.10"
+ resolved "https://registry.yarnpkg.com/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz#586db8101db30cb4438eb546737a41aad0cf13d5"
+
+nodemailer@^2.5.0:
+ version "2.7.2"
+ resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-2.7.2.tgz#f242e649aeeae39b6c7ed740ef7b061c404d30f9"
+ dependencies:
+ libmime "3.0.0"
+ mailcomposer "4.0.1"
+ nodemailer-direct-transport "3.3.2"
+ nodemailer-shared "1.1.0"
+ nodemailer-smtp-pool "2.8.2"
+ nodemailer-smtp-transport "2.7.2"
+ socks "1.1.9"
+
nodemon@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.11.0.tgz#226c562bd2a7b13d3d7518b49ad4828a3623d06c"
@@ -4555,14 +5234,10 @@ number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
-oauth-sign@~0.8.1:
+oauth-sign@~0.8.1, oauth-sign@~0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
-object-assign@4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
-
object-assign@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
@@ -4643,10 +5318,6 @@ optionator@^0.8.1, optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
-options@>=0.0.5:
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
-
original@>=0.0.5:
version "1.0.0"
resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
@@ -4657,6 +5328,10 @@ os-browserify@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f"
+os-browserify@~0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
+
os-homedir@^1.0.0, os-homedir@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
@@ -4675,7 +5350,7 @@ os-locale@^2.0.0:
lcid "^1.0.0"
mem "^1.1.0"
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
+os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@@ -4714,6 +5389,30 @@ p-timeout@^1.1.1:
dependencies:
p-finally "^1.0.0"
+pac-proxy-agent@1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz#34a385dfdf61d2f0ecace08858c745d3e791fd4d"
+ dependencies:
+ agent-base "2"
+ debug "2"
+ extend "3"
+ get-uri "2"
+ http-proxy-agent "1"
+ https-proxy-agent "1"
+ pac-resolver "~2.0.0"
+ raw-body "2"
+ socks-proxy-agent "2"
+
+pac-resolver@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-2.0.0.tgz#99b88d2f193fbdeefc1c9a529c1f3260ab5277cd"
+ dependencies:
+ co "~3.0.6"
+ degenerator "~1.0.2"
+ ip "1.0.1"
+ netmask "~1.0.4"
+ thunkify "~2.1.1"
+
package-json@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0"
@@ -4729,6 +5428,16 @@ pako@~1.0.2:
version "1.0.5"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.5.tgz#d2205dfe5b9da8af797e7c163db4d1f84e4600bc"
+pako@~1.0.5:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
+
+parents@^1.0.0, parents@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751"
+ dependencies:
+ path-platform "~0.11.15"
+
parse-asn1@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.0.0.tgz#35060f6d5015d37628c770f4e091a0b5a278bc23"
@@ -4754,12 +5463,6 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
-parsejson@0.0.3:
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab"
- dependencies:
- better-assert "~1.0.0"
-
parseqs@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
@@ -4776,7 +5479,7 @@ parseurl@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
-path-browserify@0.0.0:
+path-browserify@0.0.0, path-browserify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
@@ -4806,6 +5509,16 @@ path-parse@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
+path-platform@~0.11.15:
+ version "0.11.15"
+ resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2"
+
+path-proxy@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/path-proxy/-/path-proxy-1.0.0.tgz#18e8a36859fc9d2f1a53b48dee138543c020de5e"
+ dependencies:
+ inflection "~1.3.0"
+
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@@ -4840,6 +5553,10 @@ performance-now@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
+performance-now@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+
pify@^2.0.0, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -5155,6 +5872,14 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
source-map "^0.5.6"
supports-color "^3.2.3"
+postcss@^6.0.14:
+ version "6.0.15"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.15.tgz#f460cd6269fede0d1bf6defff0b934a9845d974d"
+ dependencies:
+ chalk "^2.3.0"
+ source-map "^0.6.1"
+ supports-color "^5.1.0"
+
postcss@^6.0.8:
version "6.0.14"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.14.tgz#5534c72114739e75d0afcf017db853099f562885"
@@ -5212,6 +5937,19 @@ proxy-addr@~1.1.5:
forwarded "~0.1.0"
ipaddr.js "1.4.0"
+proxy-agent@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.0.0.tgz#57eb5347aa805d74ec681cb25649dba39c933499"
+ dependencies:
+ agent-base "2"
+ debug "2"
+ extend "3"
+ http-proxy-agent "1"
+ https-proxy-agent "1"
+ lru-cache "~2.6.5"
+ pac-proxy-agent "1"
+ socks-proxy-agent "2"
+
prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
@@ -5240,7 +5978,7 @@ punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
-punycode@^1.2.4, punycode@^1.4.1:
+punycode@1.4.1, punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
@@ -5248,6 +5986,10 @@ q@^1.1.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
+q@~1.4.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
+
qjobs@^1.1.4:
version "1.1.5"
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73"
@@ -5260,6 +6002,14 @@ qs@6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49"
+qs@~6.2.0:
+ version "6.2.3"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe"
+
+qs@~6.5.1:
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
+
query-string@^4.1.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.2.tgz#ec0fd765f58a50031a3968c2431386f8947a5cdd"
@@ -5267,7 +6017,7 @@ query-string@^4.1.0:
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
-querystring-es3@^0.2.0:
+querystring-es3@^0.2.0, querystring-es3@~0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -5294,6 +6044,19 @@ randombytes@^2.0.0, randombytes@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
+randombytes@^2.0.5:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
+ dependencies:
+ safe-buffer "^5.1.0"
+
+randomfill@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62"
+ dependencies:
+ randombytes "^2.0.5"
+ safe-buffer "^5.1.0"
+
range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
@@ -5310,6 +6073,15 @@ raven-js@^3.14.0:
dependencies:
json-stringify-safe "^5.0.1"
+raw-body@2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
+ dependencies:
+ bytes "3.0.0"
+ http-errors "1.6.2"
+ iconv-lite "0.4.19"
+ unpipe "1.0.0"
+
raw-body@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96"
@@ -5353,6 +6125,12 @@ read-all-stream@^3.0.0:
pinkie-promise "^2.0.0"
readable-stream "^2.0.0"
+read-only-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0"
+ dependencies:
+ readable-stream "^2.0.2"
+
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@@ -5383,7 +6161,16 @@ read-pkg@^2.0.0:
normalize-package-data "^2.3.2"
path-type "^2.0.0"
-readable-stream@^2.0.0, readable-stream@^2.0.6, readable-stream@^2.1.0, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.9:
+readable-stream@1.1.x:
+ version "1.1.14"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
+readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.6, readable-stream@^2.1.0, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9, readable-stream@^2.3.0:
version "2.3.3"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
dependencies:
@@ -5395,7 +6182,7 @@ readable-stream@^2.0.0, readable-stream@^2.0.6, readable-stream@^2.1.0, readable
string_decoder "~1.0.3"
util-deprecate "~1.0.1"
-readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.0.6:
+readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.0.0, readable-stream@~2.0.5, readable-stream@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
dependencies:
@@ -5406,15 +6193,6 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
-readable-stream@~1.0.2:
- version "1.0.34"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.1"
- isarray "0.0.1"
- string_decoder "~0.10.x"
-
readdirp@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
@@ -5451,6 +6229,22 @@ redent@^1.0.0:
indent-string "^2.1.0"
strip-indent "^1.0.1"
+redis-commands@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b"
+
+redis-parser@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
+
+redis@^2.7.1:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02"
+ dependencies:
+ double-ended-queue "^2.1.0-0"
+ redis-commands "^1.2.0"
+ redis-parser "^2.6.0"
+
reduce-css-calc@^1.2.6:
version "1.3.0"
resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
@@ -5548,6 +6342,59 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
+request@2.75.x:
+ version "2.75.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.75.0.tgz#d2b8268a286da13eaa5d01adf5d18cc90f657d93"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ bl "~1.1.2"
+ caseless "~0.11.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~2.0.0"
+ har-validator "~2.0.6"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ node-uuid "~1.4.7"
+ oauth-sign "~0.8.1"
+ qs "~6.2.0"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "~0.4.1"
+
+request@^2.0.0, request@^2.74.0:
+ version "2.83.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
+ dependencies:
+ aws-sign2 "~0.7.0"
+ aws4 "^1.6.0"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.1"
+ forever-agent "~0.6.1"
+ form-data "~2.3.1"
+ har-validator "~5.0.3"
+ hawk "~6.0.2"
+ http-signature "~1.2.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.17"
+ oauth-sign "~0.8.2"
+ performance-now "^2.1.0"
+ qs "~6.5.1"
+ safe-buffer "^5.1.1"
+ stringstream "~0.0.5"
+ tough-cookie "~2.3.3"
+ tunnel-agent "^0.6.0"
+ uuid "^3.1.0"
+
request@^2.81.0:
version "2.81.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
@@ -5575,6 +6422,15 @@ request@^2.81.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
+requestretry@^1.2.2:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/requestretry/-/requestretry-1.12.2.tgz#13ce38a4ce4e809f3c9ec6d4ca3b7b9ba4acf26c"
+ dependencies:
+ extend "^3.0.0"
+ lodash "^4.15.0"
+ request "^2.74.0"
+ when "^3.7.7"
+
require-all@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/require-all/-/require-all-2.2.0.tgz#b4420c233ac0282d0ff49b277fb880a8b5de0894"
@@ -5606,11 +6462,11 @@ resolve-from@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
-resolve@1.1.x:
+resolve@1.1.7, resolve@1.1.x:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
-resolve@^1.1.6, resolve@^1.2.0, resolve@^1.4.0:
+resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.2.0, resolve@^1.4.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
dependencies:
@@ -5641,7 +6497,7 @@ right-align@^0.1.1:
dependencies:
align-text "^0.1.1"
-rimraf@2, rimraf@^2.2.8, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.6.0, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.0, rimraf@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
dependencies:
@@ -5661,7 +6517,7 @@ rx-lite@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
-safe-buffer@5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -5669,6 +6525,18 @@ safe-buffer@^5.0.1, safe-buffer@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+sanitize-html@^1.16.1:
+ version "1.16.3"
+ resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.16.3.tgz#96c1b44a36ff7312e1c22a14b05274370ac8bd56"
+ dependencies:
+ htmlparser2 "^3.9.0"
+ lodash.clonedeep "^4.5.0"
+ lodash.escaperegexp "^4.1.2"
+ lodash.mergewith "^4.6.0"
+ postcss "^6.0.14"
+ srcset "^1.0.0"
+ xtend "^4.0.0"
+
sax@~1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
@@ -5707,9 +6575,9 @@ semver-diff@^2.0.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
-semver@~4.3.3:
- version "4.3.6"
- resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
+semver@~5.0.1:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
send@0.15.4:
version "0.15.4"
@@ -5772,6 +6640,20 @@ sha.js@^2.3.6:
dependencies:
inherits "^2.0.1"
+sha.js@~2.4.4:
+ version "2.4.9"
+ resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d"
+ dependencies:
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+shasum@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f"
+ dependencies:
+ json-stable-stringify "~0.0.0"
+ sha.js "~2.4.4"
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -5782,6 +6664,15 @@ shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+shell-quote@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
+ dependencies:
+ array-filter "~0.0.0"
+ array-map "~0.0.0"
+ array-reduce "~0.0.0"
+ jsonify "~0.0.0"
+
shelljs@^0.7.5:
version "0.7.8"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
@@ -5794,6 +6685,12 @@ signal-exit@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+slack-node@~0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/slack-node/-/slack-node-0.2.0.tgz#de4b8dddaa8b793f61dbd2938104fdabf37dfa30"
+ dependencies:
+ requestretry "^1.2.2"
+
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -5806,55 +6703,69 @@ slide@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
+smart-buffer@^1.0.13, smart-buffer@^1.0.4:
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16"
+
+smtp-connection@2.12.0:
+ version "2.12.0"
+ resolved "https://registry.yarnpkg.com/smtp-connection/-/smtp-connection-2.12.0.tgz#d76ef9127cb23c2259edb1e8349c2e8d5e2d74c1"
+ dependencies:
+ httpntlm "1.6.1"
+ nodemailer-shared "1.1.0"
+
sntp@1.x.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
dependencies:
hoek "2.x.x"
-socket.io-adapter@0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b"
+sntp@2.x.x:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
dependencies:
- debug "2.3.3"
- socket.io-parser "2.3.1"
+ hoek "4.x.x"
-socket.io-client@1.7.3:
- version "1.7.3"
- resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.3.tgz#b30e86aa10d5ef3546601c09cde4765e381da377"
+socket.io-adapter@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"
+
+socket.io-client@2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.0.4.tgz#0918a552406dc5e540b380dcd97afc4a64332f8e"
dependencies:
backo2 "1.0.2"
+ base64-arraybuffer "0.1.5"
component-bind "1.0.0"
component-emitter "1.2.1"
- debug "2.3.3"
- engine.io-client "1.8.3"
- has-binary "0.1.7"
+ debug "~2.6.4"
+ engine.io-client "~3.1.0"
+ has-cors "1.1.0"
indexof "0.0.1"
object-component "0.0.3"
+ parseqs "0.0.5"
parseuri "0.0.5"
- socket.io-parser "2.3.1"
+ socket.io-parser "~3.1.1"
to-array "0.1.4"
-socket.io-parser@2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0"
+socket.io-parser@~3.1.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.1.2.tgz#dbc2282151fc4faebbe40aeedc0772eba619f7f2"
dependencies:
- component-emitter "1.1.2"
- debug "2.2.0"
- isarray "0.0.1"
- json3 "3.3.2"
+ component-emitter "1.2.1"
+ debug "~2.6.4"
+ has-binary2 "~1.0.2"
+ isarray "2.0.1"
-socket.io@1.7.3:
- version "1.7.3"
- resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.3.tgz#b8af9caba00949e568e369f1327ea9be9ea2461b"
+socket.io@2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.0.4.tgz#c1a4590ceff87ecf13c72652f046f716b29e6014"
dependencies:
- debug "2.3.3"
- engine.io "1.8.3"
- has-binary "0.1.7"
- object-assign "4.1.0"
- socket.io-adapter "0.5.0"
- socket.io-client "1.7.3"
- socket.io-parser "2.3.1"
+ debug "~2.6.6"
+ engine.io "~3.1.0"
+ socket.io-adapter "~1.1.0"
+ socket.io-client "2.0.4"
+ socket.io-parser "~3.1.1"
sockjs-client@1.0.1:
version "1.0.1"
@@ -5885,6 +6796,28 @@ sockjs@0.3.18:
faye-websocket "^0.10.0"
uuid "^2.0.2"
+socks-proxy-agent@2:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz#86ebb07193258637870e13b7bd99f26c663df3d3"
+ dependencies:
+ agent-base "2"
+ extend "3"
+ socks "~1.1.5"
+
+socks@1.1.9:
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.9.tgz#628d7e4d04912435445ac0b6e459376cb3e6d691"
+ dependencies:
+ ip "^1.1.2"
+ smart-buffer "^1.0.4"
+
+socks@~1.1.5:
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a"
+ dependencies:
+ ip "^1.1.4"
+ smart-buffer "^1.0.13"
+
sort-keys@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
@@ -5909,12 +6842,6 @@ source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, sourc
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
-source-map@^0.1.41:
- version "0.1.43"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
- dependencies:
- amdefine ">=0.0.4"
-
source-map@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
@@ -5931,6 +6858,10 @@ source-map@~0.2.0:
dependencies:
amdefine ">=0.0.4"
+source-map@~0.5.6:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+
spdx-correct@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
@@ -5982,6 +6913,13 @@ sql.js@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/sql.js/-/sql.js-0.4.0.tgz#23be9635520eb0ff43a741e7e830397266e88445"
+srcset@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
+ dependencies:
+ array-uniq "^1.0.2"
+ number-is-nan "^1.0.0"
+
sshpk@^1.7.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
@@ -6000,19 +6938,36 @@ sshpk@^1.7.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
-stream-browserify@^2.0.1:
+stream-browserify@^2.0.0, stream-browserify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
dependencies:
inherits "~2.0.1"
readable-stream "^2.0.2"
+stream-combiner2@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe"
+ dependencies:
+ duplexer2 "~0.1.0"
+ readable-stream "^2.0.2"
+
stream-combiner@~0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14"
dependencies:
duplexer "~0.1.1"
+stream-http@^2.0.0:
+ version "2.7.2"
+ resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad"
+ dependencies:
+ builtin-status-codes "^3.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.2.6"
+ to-arraybuffer "^1.0.0"
+ xtend "^4.0.0"
+
stream-http@^2.3.1:
version "2.6.3"
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.6.3.tgz#4c3ddbf9635968ea2cfd4e48d43de5def2625ac3"
@@ -6027,6 +6982,22 @@ stream-shift@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+stream-splicer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83"
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.2"
+
+streamroller@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.7.0.tgz#a1d1b7cf83d39afb0d63049a5acbf93493bdf64b"
+ dependencies:
+ date-format "^1.2.0"
+ debug "^3.1.0"
+ mkdirp "^0.5.1"
+ readable-stream "^2.3.0"
+
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
@@ -6064,13 +7035,13 @@ string_decoder@^0.10.25, string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
-string_decoder@~1.0.3:
+string_decoder@~1.0.0, string_decoder@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
dependencies:
safe-buffer "~5.1.0"
-stringstream@~0.0.4:
+stringstream@~0.0.4, stringstream@~0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
@@ -6080,6 +7051,12 @@ strip-ansi@3.0.1, strip-ansi@^3.0.0, strip-ansi@^3.0.1:
dependencies:
ansi-regex "^2.0.0"
+strip-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+ dependencies:
+ ansi-regex "^3.0.0"
+
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
@@ -6104,6 +7081,12 @@ strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+subarg@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
+ dependencies:
+ minimist "^1.1.0"
+
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -6126,6 +7109,12 @@ supports-color@^4.2.1:
dependencies:
has-flag "^2.0.0"
+supports-color@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5"
+ dependencies:
+ has-flag "^2.0.0"
+
svg4everybody@2.1.9:
version "2.1.9"
resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d"
@@ -6142,6 +7131,12 @@ svgo@^0.7.0:
sax "~1.2.1"
whet.extend "~0.9.9"
+syntax-error@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.3.0.tgz#1ed9266c4d40be75dc55bf9bb1cb77062bb96ca1"
+ dependencies:
+ acorn "^4.0.3"
+
table@^3.7.8:
version "3.8.3"
resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
@@ -6226,14 +7221,29 @@ three@^0.84.0:
version "0.84.0"
resolved "https://registry.yarnpkg.com/three/-/three-0.84.0.tgz#95be85a55a0fa002aa625ed559130957dcffd918"
-through@2, through@^2.3.6, through@~2.3, through@~2.3.1, through@~2.3.4, through@~2.3.8:
+through2@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
+ dependencies:
+ readable-stream "^2.1.5"
+ xtend "~4.0.1"
+
+through@2, "through@>=2.2.7 <3", through@^2.3.6, through@~2.3, through@~2.3.1, through@~2.3.4, through@~2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+thunkify@~2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
+
thunky@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
+time-stamp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357"
+
timeago.js@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-3.0.2.tgz#32a67e7c0d887ea42ca588d3aae26f77de5e76cc"
@@ -6248,7 +7258,7 @@ timed-out@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
-timers-browserify@^1.4.2:
+timers-browserify@^1.0.1, timers-browserify@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d"
dependencies:
@@ -6260,11 +7270,21 @@ timers-browserify@^2.0.2:
dependencies:
setimmediate "^1.0.4"
+timespan@2.3.x:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/timespan/-/timespan-2.3.0.tgz#4902ce040bd13d845c8f59b27e9d59bad6f39929"
+
tiny-emitter@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
-tmp@0.0.31, tmp@0.0.x:
+tmp@0.0.33:
+ version "0.0.33"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+ dependencies:
+ os-tmpdir "~1.0.2"
+
+tmp@0.0.x:
version "0.0.31"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
dependencies:
@@ -6298,6 +7318,12 @@ tough-cookie@~2.3.0:
dependencies:
punycode "^1.4.1"
+tough-cookie@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
+ dependencies:
+ punycode "^1.4.1"
+
traverse@0.6.6:
version "0.6.6"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
@@ -6314,7 +7340,11 @@ tryit@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
-tty-browserify@0.0.0:
+tsscmp@~1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.5.tgz#7dc4a33af71581ab4337da91d85ca5427ebd9a97"
+
+tty-browserify@0.0.0, tty-browserify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -6324,6 +7354,10 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
+tunnel-agent@~0.4.1:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
+
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -6341,7 +7375,7 @@ type-is@~1.6.15:
media-typer "0.3.0"
mime-types "~2.1.15"
-typedarray@^0.0.6:
+typedarray@^0.0.6, typedarray@~0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -6370,14 +7404,14 @@ uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
-ultron@1.0.x:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
-
ultron@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
+umd@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.1.tgz#8ae556e11011f63c2596708a8837259f01b3d60e"
+
unc-path-regex@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
@@ -6390,6 +7424,10 @@ underscore@^1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
+underscore@~1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
+
uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
@@ -6458,7 +7496,7 @@ url-to-options@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
-url@^0.11.0:
+url@^0.11.0, url@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
dependencies:
@@ -6482,7 +7520,7 @@ util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
-util@0.10.3, util@^0.10.3:
+util@0.10.3, util@^0.10.3, util@~0.10.1:
version "0.10.3"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
dependencies:
@@ -6496,10 +7534,14 @@ uuid@^2.0.1, uuid@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
-uuid@^3.0.0:
+uuid@^3.0.0, uuid@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
+uws@~0.14.4:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/uws/-/uws-0.14.5.tgz#67aaf33c46b2a587a5f6666d00f7691328f149dc"
+
validate-npm-package-license@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
@@ -6527,7 +7569,7 @@ visibilityjs@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/visibilityjs/-/visibilityjs-1.2.4.tgz#bff8663da62c8c10ad4ee5ae6a1ae6fac4259d63"
-vm-browserify@0.0.4:
+vm-browserify@0.0.4, vm-browserify@~0.0.1:
version "0.0.4"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
dependencies:
@@ -6636,7 +7678,7 @@ webpack-bundle-analyzer@^2.8.2:
opener "^1.4.3"
ws "^2.3.1"
-webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.11.0:
+webpack-dev-middleware@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9"
dependencies:
@@ -6645,6 +7687,16 @@ webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.11.0:
path-is-absolute "^1.0.0"
range-parser "^1.0.3"
+webpack-dev-middleware@^1.12.0:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e"
+ dependencies:
+ memory-fs "~0.4.1"
+ mime "^1.5.0"
+ path-is-absolute "^1.0.0"
+ range-parser "^1.0.3"
+ time-stamp "^2.0.0"
+
webpack-dev-server@^2.6.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.7.1.tgz#21580f5a08cd065c71144cf6f61c345bca59a8b8"
@@ -6721,6 +7773,10 @@ websocket-extensions@>=0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
+when@^3.7.7:
+ version "3.7.8"
+ resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82"
+
whet.extend@~0.9.9:
version "0.9.9"
resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
@@ -6793,13 +7849,6 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
-ws@1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f"
- dependencies:
- options ">=0.0.5"
- ultron "1.0.x"
-
ws@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-2.3.1.tgz#6b94b3e447cb6a363f785eaf94af6359e8e81c80"
@@ -6807,9 +7856,13 @@ ws@^2.3.1:
safe-buffer "~5.0.1"
ultron "~1.1.0"
-wtf-8@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"
+ws@~3.3.1:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
+ dependencies:
+ async-limiter "~1.0.0"
+ safe-buffer "~5.1.0"
+ ultron "~1.1.0"
xdg-basedir@^2.0.0:
version "2.0.0"
@@ -6817,11 +7870,15 @@ xdg-basedir@^2.0.0:
dependencies:
os-homedir "^1.0.0"
-xmlhttprequest-ssl@1.5.3:
- version "1.5.3"
- resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"
+xmlhttprequest-ssl@~1.5.4:
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
+
+xregexp@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
-xtend@^4.0.0:
+xtend@^4.0.0, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"