summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Greiling <mike@pixelcog.com>2018-10-24 15:12:38 +0000
committerMike Greiling <mike@pixelcog.com>2018-10-24 15:12:38 +0000
commita6af6bf6e21bb5b36a94ba334c99bc586cb3297b (patch)
tree41a63f8d85f562573f45aa5898d749ceb51606a5
parentac8f85805c0ff7dd00072b2974fdd05724863816 (diff)
parenta1ee2072f1a7c197e13bd2d5f8ca59ad1deb1c49 (diff)
downloadgitlab-ce-prettify-all-the-things-7.tar.gz
Merge branch 'master' into 'prettify-all-the-things-7'prettify-all-the-things-7
# Conflicts: # app/assets/javascripts/activities.js
-rw-r--r--.gitlab-ci.yml95
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md4
-rw-r--r--.gitlab/issue_templates/Test plan.md36
-rw-r--r--.gitlab/merge_request_templates/Database changes.md28
-rw-r--r--.gitlab/merge_request_templates/Documentation.md1
-rw-r--r--.prettierignore4
-rw-r--r--CHANGELOG.md225
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Dangerfile1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock4
-rw-r--r--Gemfile.rails5.lock4
-rw-r--r--README.md2
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/activities.js8
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js2
-rw-r--r--app/assets/javascripts/compare_autocomplete.js2
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue116
-rw-r--r--app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js5
-rw-r--r--app/assets/javascripts/flash.js9
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue38
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue6
-rw-r--r--app/assets/javascripts/jobs/components/job_container_item.vue65
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue2
-rw-r--r--app/assets/javascripts/jobs/components/jobs_container.vue53
-rw-r--r--app/assets/javascripts/merge_request_tabs.js2
-rw-r--r--app/assets/javascripts/mr_notes/index.js2
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue5
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue82
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue11
-rw-r--r--app/assets/javascripts/notes/discussion_filters.js33
-rw-r--r--app/assets/javascripts/notes/index.js3
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js5
-rw-r--r--app/assets/javascripts/notes/stores/actions.js25
-rw-r--r--app/assets/javascripts/notes/stores/getters.js2
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js4
-rw-r--r--app/assets/javascripts/pager.js11
-rw-r--r--app/assets/javascripts/pages/groups/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/reports/components/grouped_test_reports_app.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue75
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue18
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue34
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue143
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue28
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss11
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss1
-rw-r--r--app/assets/stylesheets/framework/header.scss19
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss1
-rw-r--r--app/assets/stylesheets/framework/page_title.scss18
-rw-r--r--app/assets/stylesheets/framework/panels.scss5
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss16
-rw-r--r--app/assets/stylesheets/framework/terms.scss15
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/diff.scss14
-rw-r--r--app/assets/stylesheets/pages/issues.scss23
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss25
-rw-r--r--app/assets/stylesheets/pages/notes.scss21
-rw-r--r--app/controllers/admin/applications_controller.rb8
-rw-r--r--app/controllers/admin/dashboard_controller.rb4
-rw-r--r--app/controllers/autocomplete_controller.rb2
-rw-r--r--app/controllers/boards/issues_controller.rb17
-rw-r--r--app/controllers/concerns/issuable_actions.rb33
-rw-r--r--app/controllers/concerns/notes_actions.rb17
-rw-r--r--app/controllers/import/github_controller.rb2
-rw-r--r--app/controllers/profiles/keys_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb43
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb6
-rw-r--r--app/finders/applications_finder.rb22
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/finders/notes_finder.rb6
-rw-r--r--app/finders/user_finder.rb52
-rw-r--r--app/finders/users_finder.rb4
-rw-r--r--app/helpers/blob_helper.rb10
-rw-r--r--app/helpers/ci_status_helper.rb2
-rw-r--r--app/helpers/count_helper.rb14
-rw-r--r--app/helpers/groups_helper.rb9
-rw-r--r--app/helpers/issuables_helper.rb4
-rw-r--r--app/helpers/time_helper.rb16
-rw-r--r--app/models/ci/job_artifact.rb27
-rw-r--r--app/models/ci/pipeline.rb10
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb3
-rw-r--r--app/models/clusters/kubernetes_namespace.rb49
-rw-r--r--app/models/clusters/platforms/kubernetes.rb16
-rw-r--r--app/models/clusters/project.rb3
-rw-r--r--app/models/concerns/relative_positioning.rb79
-rw-r--r--app/models/environment_status.rb56
-rw-r--r--app/models/forked_project_link.rb6
-rw-r--r--app/models/list.rb1
-rw-r--r--app/models/note.rb9
-rw-r--r--app/models/project.rb105
-rw-r--r--app/models/project_services/bamboo_service.rb13
-rw-r--r--app/models/project_services/microsoft_teams_service.rb2
-rw-r--r--app/models/user.rb10
-rw-r--r--app/models/user_preference.rb52
-rw-r--r--app/presenters/ci/build_runner_presenter.rb10
-rw-r--r--app/serializers/current_user_entity.rb8
-rw-r--r--app/serializers/environment_status_entity.rb62
-rw-r--r--app/serializers/environment_status_serializer.rb5
-rw-r--r--app/serializers/merge_request_user_entity.rb2
-rw-r--r--app/serializers/user_preference_entity.rb10
-rw-r--r--app/services/audit_event_service.rb26
-rw-r--r--app/services/boards/lists/list_service.rb2
-rw-r--r--app/services/groups/update_service.rb6
-rw-r--r--app/services/projects/after_rename_service.rb135
-rw-r--r--app/services/projects/create_service.rb21
-rw-r--r--app/services/projects/fork_service.rb68
-rw-r--r--app/services/projects/forks_count_service.rb5
-rw-r--r--app/services/projects/move_forks_service.rb13
-rw-r--r--app/services/projects/unlink_fork_service.rb1
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/services/quick_actions/interpret_service.rb2
-rw-r--r--app/services/web_hook_service.rb8
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml9
-rw-r--r--app/views/admin/projects/show.html.haml2
-rw-r--r--app/views/dashboard/_activity_head.html.haml3
-rw-r--r--app/views/dashboard/_groups_head.html.haml9
-rw-r--r--app/views/dashboard/_projects_head.html.haml9
-rw-r--r--app/views/dashboard/_snippets_head.html.haml11
-rw-r--r--app/views/dashboard/issues.html.haml8
-rw-r--r--app/views/dashboard/merge_requests.html.haml9
-rw-r--r--app/views/dashboard/milestones/index.html.haml12
-rw-r--r--app/views/dashboard/todos/index.html.haml3
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/new.html.haml3
-rw-r--r--app/views/layouts/_flash.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml5
-rw-r--r--app/views/layouts/dashboard.html.haml1
-rw-r--r--app/views/layouts/explore.html.haml2
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml15
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml14
-rw-r--r--app/views/layouts/terms.html.haml29
-rw-r--r--app/views/projects/_md_preview.html.haml4
-rw-r--r--app/views/projects/_wiki.html.haml16
-rw-r--r--app/views/projects/blob/_template_selectors.html.haml8
-rw-r--r--app/views/projects/blob/edit.html.haml2
-rw-r--r--app/views/projects/blob/new.html.haml2
-rw-r--r--app/views/projects/branches/_panel.html.haml3
-rw-r--r--app/views/projects/ci/builds/_build.html.haml4
-rw-r--r--app/views/projects/issues/_discussion.html.haml2
-rw-r--r--app/views/projects/issues/_new_branch.html.haml3
-rw-r--r--app/views/projects/issues/show.html.haml7
-rw-r--r--app/views/projects/merge_requests/show.html.haml6
-rw-r--r--app/views/projects/new.html.haml3
-rw-r--r--app/views/projects/pipelines/_info.html.haml6
-rw-r--r--app/views/projects/protected_branches/shared/_branches_list.html.haml3
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml3
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml3
-rw-r--r--app/views/projects/protected_tags/shared/_tags_list.html.haml3
-rw-r--r--app/views/projects/registry/repositories/index.html.haml3
-rw-r--r--app/views/projects/services/prometheus/_metrics.html.haml12
-rw-r--r--app/views/projects/tags/new.html.haml3
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/projects/triggers/_index.html.haml3
-rw-r--r--app/views/shared/_user_dropdown_contributing_link.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml15
-rw-r--r--app/views/shared/issuable/_sidebar_todo.html.haml4
-rw-r--r--app/views/shared/runners/show.html.haml2
-rw-r--r--app/views/snippets/new.html.haml13
-rw-r--r--app/workers/namespaceless_project_destroy_worker.rb2
-rwxr-xr-xbin/profile-url2
-rw-r--r--changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml5
-rw-r--r--changelogs/unreleased/-50928-stale-list-of-issues-on-board-after-click-back.yml5
-rw-r--r--changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml5
-rw-r--r--changelogs/unreleased/1801-allow-event_filter-to-be-set-in-the-url.yml5
-rw-r--r--changelogs/unreleased/21307-send-deployment-information-in-job-api.yml5
-rw-r--r--changelogs/unreleased/21617-initialize-projects-with-readme.yml5
-rw-r--r--changelogs/unreleased/21970-fix-bamboo-results.yml5
-rw-r--r--changelogs/unreleased/22104-fix-environment-name-overlap.yml4
-rw-r--r--changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml5
-rw-r--r--changelogs/unreleased/23197-add-custom-header-for-error-responses.yml6
-rw-r--r--changelogs/unreleased/23986-choose-commit-email.yml5
-rw-r--r--changelogs/unreleased/24128-fix-comment-unresolve-discussions.yml5
-rw-r--r--changelogs/unreleased/26723-discussion-filters.yml5
-rw-r--r--changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml5
-rw-r--r--changelogs/unreleased/31887-remove-images-from-todos.yml5
-rw-r--r--changelogs/unreleased/32959-update-todo-icon.yml5
-rw-r--r--changelogs/unreleased/35476-lazy-image-intersectionobserver.yml6
-rw-r--r--changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml6
-rw-r--r--changelogs/unreleased/38304-username-API-call-case-sensitive.yml5
-rw-r--r--changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml5
-rw-r--r--changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml5
-rw-r--r--changelogs/unreleased/41040-long-webhook-url-problem.yml5
-rw-r--r--changelogs/unreleased/41205-fix-filtering-issues.yml5
-rw-r--r--changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml5
-rw-r--r--changelogs/unreleased/42611-removed-branch-link.yml5
-rw-r--r--changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml5
-rw-r--r--changelogs/unreleased/43422-Update-images-in-group-docs.yml5
-rw-r--r--changelogs/unreleased/43832-adds-chdmod-to-commits-actions-api.yml5
-rw-r--r--changelogs/unreleased/44596-double-title-merge-request-message.yml5
-rw-r--r--changelogs/unreleased/44627-add-link-md-editor.yml5
-rw-r--r--changelogs/unreleased/44768-lazy-load-xterm-css.yml5
-rw-r--r--changelogs/unreleased/44998-split-admin-settings-into-multiple-sub-pages.yml5
-rw-r--r--changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml5
-rw-r--r--changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml5
-rw-r--r--changelogs/unreleased/45754-issue-mr-and-archived-projects.yml5
-rw-r--r--changelogs/unreleased/45754-open-issues-from-archived-project-listed-in-group-issue-board.yml5
-rw-r--r--changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml5
-rw-r--r--changelogs/unreleased/46733-move-filter-dropdown-from-font-awesome-to-our-own-icons.yml5
-rw-r--r--changelogs/unreleased/46884-remove-card-title.yml5
-rw-r--r--changelogs/unreleased/47398-user-is-unable-revoke-a-authorized-application-unless-user-oauth-applications-is-checked-in-admin-settings.yml6
-rw-r--r--changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml5
-rw-r--r--changelogs/unreleased/48004-db-initialize-migrate.yml5
-rw-r--r--changelogs/unreleased/48222-fix-todos-status-button.yml6
-rw-r--r--changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml6
-rw-r--r--changelogs/unreleased/48731-show-empty-state-on-wiki-only-projects.yml6
-rw-r--r--changelogs/unreleased/48902-fix-diff-vertical-alignment.yml5
-rw-r--r--changelogs/unreleased/4907-improve-build-create-performance-for-license-management.yml5
-rw-r--r--changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml5
-rw-r--r--changelogs/unreleased/49329-mr-show-commit-details.yml5
-rw-r--r--changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml5
-rw-r--r--changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml5
-rw-r--r--changelogs/unreleased/49990-enable-omniauth-by-default.yml5
-rw-r--r--changelogs/unreleased/50111-improve-design-of-cluster-apps-to-handle-larger-quantity.yml5
-rw-r--r--changelogs/unreleased/50161-hide-close-mr-button-when-merged.yml5
-rw-r--r--changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml6
-rw-r--r--changelogs/unreleased/50289-vendor-ci-yml-for-the-last-time.yml5
-rw-r--r--changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml5
-rw-r--r--changelogs/unreleased/50461-add-retried-builds-in-pipeline-stage-endpoint.yml5
-rw-r--r--changelogs/unreleased/50552-unable-to-close-performance-bar.yml5
-rw-r--r--changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml5
-rw-r--r--changelogs/unreleased/50678-ignores-project-pending-delete.yml5
-rw-r--r--changelogs/unreleased/50728-re-arrange-help-related-user-menu-items-into-new-help-menu.yml5
-rw-r--r--changelogs/unreleased/50808-choosing-initialize-repo-with-a-readme-breaks-project-created-from-template.yml5
-rw-r--r--changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml5
-rw-r--r--changelogs/unreleased/50904-move-job-page-vue.yml5
-rw-r--r--changelogs/unreleased/50904-update-scroll-utils.yml5
-rw-r--r--changelogs/unreleased/50904-use-vuex-store-job.yml5
-rw-r--r--changelogs/unreleased/50904-vuex-show-block.yml5
-rw-r--r--changelogs/unreleased/50989-add-trigger-information-to-job-api.yml5
-rw-r--r--changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml5
-rw-r--r--changelogs/unreleased/51021-more-attr-encrypted.yml5
-rw-r--r--changelogs/unreleased/51050-fix.yml5
-rw-r--r--changelogs/unreleased/51112-add-status-illustration-in-job-api.yml5
-rw-r--r--changelogs/unreleased/51273-expose-runners-for-build-in-job-api.yml5
-rw-r--r--changelogs/unreleased/51282-link-to-user-snippets-on-new-user-snippet-page.yml5
-rw-r--r--changelogs/unreleased/51386-broken-border-reports.yml5
-rw-r--r--changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml5
-rw-r--r--changelogs/unreleased/51450-vendor-refactor-registry-login.yml5
-rw-r--r--changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml5
-rw-r--r--changelogs/unreleased/51509-remove-sidekiq-limit-fetch.yml5
-rw-r--r--changelogs/unreleased/51564-fix-commit-email-usage.yml5
-rw-r--r--changelogs/unreleased/51569-performance-bar.yml5
-rw-r--r--changelogs/unreleased/51571-wrapper-rake-task-uploads-migrate-os.yml5
-rw-r--r--changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml5
-rw-r--r--changelogs/unreleased/51716-add-kubernetes-namespace-model.yml5
-rw-r--r--changelogs/unreleased/51725-push-mirrors-default-branch-reset-to-master.yml5
-rw-r--r--changelogs/unreleased/51748-filter-any-milestone-via-api.yml5
-rw-r--r--changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml5
-rw-r--r--changelogs/unreleased/51839-remove-sorting-on-project-tags.yml5
-rw-r--r--changelogs/unreleased/51925-expose-has_trace-in-job-api.yml5
-rw-r--r--changelogs/unreleased/51942-auto-devops-local-tiller.yml5
-rw-r--r--changelogs/unreleased/51955-change-single-item-breadcrumbs-to-page-titles.yml5
-rw-r--r--changelogs/unreleased/52035-selecting-an-autofill-suggestion-for-project-name-will-not-update-the-project-slug.yml5
-rw-r--r--changelogs/unreleased/52059-filter-milestone-by-none-any.yml5
-rw-r--r--changelogs/unreleased/52178-markdown-table-borders.yml5
-rw-r--r--changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml5
-rw-r--r--changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml5
-rw-r--r--changelogs/unreleased/52299-follow-up-from-resolve-add-status-message-from-within-user-menu.yml5
-rw-r--r--changelogs/unreleased/52361-fix-file-tree-mobile.yml5
-rw-r--r--changelogs/unreleased/52472-pipeline-endpoint-json.yml5
-rw-r--r--changelogs/unreleased/52519-runners-link.yml5
-rw-r--r--changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml5
-rw-r--r--changelogs/unreleased/52559-applications-api-get-delete.yml5
-rw-r--r--changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml5
-rw-r--r--changelogs/unreleased/52570-erased-block.yml5
-rw-r--r--changelogs/unreleased/52608-sidebar.yml5
-rw-r--r--changelogs/unreleased/52614-update-job-started-check.yml5
-rw-r--r--changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml5
-rw-r--r--changelogs/unreleased/52669-fixes-quick-actions-preview.yml5
-rw-r--r--changelogs/unreleased/52692-catch-redirect-loops.yml5
-rw-r--r--changelogs/unreleased/52772-assign-me-quick-action-doesn-t-work-if-there-is-extra-white-space.yml5
-rw-r--r--changelogs/unreleased/52840-fix-runners-details-page.yml5
-rw-r--r--changelogs/unreleased/52886-fix-broken-master.yml5
-rw-r--r--changelogs/unreleased/53013-duplicate-escape.yml5
-rw-r--r--changelogs/unreleased/53023-endless-scroll-loader-is-visible-on-user-profile-overview-page.yml4
-rw-r--r--changelogs/unreleased/5987-project-templates-api.yml5
-rw-r--r--changelogs/unreleased/6717_extend_reports_for_security_products.yml5
-rw-r--r--changelogs/unreleased/_acet-fix-diff-file-header.yml5
-rw-r--r--changelogs/unreleased/_acet-fix-placeholder-note.yml5
-rw-r--r--changelogs/unreleased/add-2fa-button.yml5
-rw-r--r--changelogs/unreleased/add-button-to-insert-table-in-markdown.yml5
-rw-r--r--changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml5
-rw-r--r--changelogs/unreleased/add-gl-link-to-download-viewer.yml5
-rw-r--r--changelogs/unreleased/add-gl-link-to-markdown-header.yml5
-rw-r--r--changelogs/unreleased/add-gl-link-to-user-avatar-link.yml5
-rw-r--r--changelogs/unreleased/add-installation-type-backup-information.yml5
-rw-r--r--changelogs/unreleased/add-most-stars-for-filter-option.yml5
-rw-r--r--changelogs/unreleased/add-new-kubernetes-spec-helpers.yml5
-rw-r--r--changelogs/unreleased/add-role-binding-to-kubeclient.yml5
-rw-r--r--changelogs/unreleased/add_reliable_fetcher.yml5
-rw-r--r--changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml5
-rw-r--r--changelogs/unreleased/align-form-labels.yml5
-rw-r--r--changelogs/unreleased/auth.yml5
-rw-r--r--changelogs/unreleased/autodevops-timed-incremental-rollout.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-bump-mermaid.yml5
-rw-r--r--changelogs/unreleased/bvl-remove-sha-from-help.yml5
-rw-r--r--changelogs/unreleased/bvl-show-pre-release-sha.yml5
-rw-r--r--changelogs/unreleased/ccr-43034_issues_controller_100_queries.yml5
-rw-r--r--changelogs/unreleased/ccr-50483_add_filter_for_group_milestones.yml5
-rw-r--r--changelogs/unreleased/ccr-wip_filter.yml5
-rw-r--r--changelogs/unreleased/change-branch-font-type-in-tag-creation.yml5
-rw-r--r--changelogs/unreleased/clean-gitlab-git.yml5
-rw-r--r--changelogs/unreleased/clone-nurtch-demo-repo.yml5
-rw-r--r--changelogs/unreleased/copy-changes-for-abuse-clarity.yml5
-rw-r--r--changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml5
-rw-r--r--changelogs/unreleased/dm-create-note-return-discussion.yml5
-rw-r--r--changelogs/unreleased/dm-fix-assign-unassign-quick-actions.yml6
-rw-r--r--changelogs/unreleased/drop-allow_overflow-option-duration_in_numbers.yml5
-rw-r--r--changelogs/unreleased/dz-labels-subscribe-filter.yml5
-rw-r--r--changelogs/unreleased/enable-force-write-auth-keys-restore.yml5
-rw-r--r--changelogs/unreleased/fa-handle_invalid_utf8_errors.yml5
-rw-r--r--changelogs/unreleased/fe-ac-review-app-changes-33418.yml5
-rw-r--r--changelogs/unreleased/feature-add-public-email-to-users-api.yml5
-rw-r--r--changelogs/unreleased/feature-flags-mvc.yml5
-rw-r--r--changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml5
-rw-r--r--changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml5
-rw-r--r--changelogs/unreleased/feature-runner-state-filter-for-admin-view.yml5
-rw-r--r--changelogs/unreleased/feature-runner-type-filter-for-admin-view.yml5
-rw-r--r--changelogs/unreleased/feature-set-public-email-through-api.yml5
-rw-r--r--changelogs/unreleased/features-unauth-access-ssh-keys.yml5
-rw-r--r--changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml5
-rw-r--r--changelogs/unreleased/fix-base64-encoded-file-uploads.yml5
-rw-r--r--changelogs/unreleased/fix-chat-notification-service-for-ee.yml5
-rw-r--r--changelogs/unreleased/fix-committer-typo.yml5
-rw-r--r--changelogs/unreleased/fix-events-finder-incomplete.yml5
-rw-r--r--changelogs/unreleased/fix-help-text-font-color-in-merge-request-creation.yml5
-rw-r--r--changelogs/unreleased/fix-leading-slash-in-redirects-plus-rubocop.yml5
-rw-r--r--changelogs/unreleased/fix-mention-in-edit-mr.yml5
-rw-r--r--changelogs/unreleased/force-post-migration-dir-schema-load.yml5
-rw-r--r--changelogs/unreleased/frozen-string-app-controller-more.yml5
-rw-r--r--changelogs/unreleased/frozen-string-app-controller.yml5
-rw-r--r--changelogs/unreleased/frozen-string-app-controllers-much-more.yml5
-rw-r--r--changelogs/unreleased/frozen-string-app-enforce.yml5
-rw-r--r--changelogs/unreleased/frozen-string-app-finders-graphql.yml5
-rw-r--r--changelogs/unreleased/frozen-string-docs.yml5
-rw-r--r--changelogs/unreleased/frozen-string-enable-app-helpers.yml5
-rw-r--r--changelogs/unreleased/frozen-string-enable-lib-gitlab.yml (renamed from changelogs/unreleased/frozen-string-enable-vestigial.yml)2
-rw-r--r--changelogs/unreleased/gt-add-transparent-background-to-markdown-header-tabs.yml5
-rw-r--r--changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml5
-rw-r--r--changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml5
-rw-r--r--changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml5
-rw-r--r--changelogs/unreleased/ide-fetch-templates-pages.yml5
-rw-r--r--changelogs/unreleased/improve-empty-project-placeholder.yml5
-rw-r--r--changelogs/unreleased/issue_50528.yml5
-rw-r--r--changelogs/unreleased/jivl-fix-bar-char-transient-spec-failure.yml5
-rw-r--r--changelogs/unreleased/jivl-fix-monitoring-dashboard-resizing-navbar.yml5
-rw-r--r--changelogs/unreleased/leipert-fix-mr-widget-header-margins.yml5
-rw-r--r--changelogs/unreleased/lfs-project-attribute-alias.yml5
-rw-r--r--changelogs/unreleased/lib-api-frozen-string-enable.yml5
-rw-r--r--changelogs/unreleased/load_project_features.yml5
-rw-r--r--changelogs/unreleased/lock-unlock-quick-actions.yml5
-rw-r--r--changelogs/unreleased/mao-48221-issues_show_sql_count.yml5
-rw-r--r--changelogs/unreleased/mk-asymmetric-exists-cache.yml6
-rw-r--r--changelogs/unreleased/more-table-widths.yml5
-rw-r--r--changelogs/unreleased/mr-creation-source-project-filtering.yml5
-rw-r--r--changelogs/unreleased/mr-file-list.yml5
-rw-r--r--changelogs/unreleased/mr-file-tree-data.yml5
-rw-r--r--changelogs/unreleased/mr-file-tree-inline-fluid-width-fix.yml5
-rw-r--r--changelogs/unreleased/mr-legacy-diff-notes.yml5
-rw-r--r--changelogs/unreleased/mr-widget-discussion-state-fix.yml5
-rw-r--r--changelogs/unreleased/osw-clean-up-phase-for-diff-files-removal.yml5
-rw-r--r--changelogs/unreleased/osw-configurable-single-diff-file-limit.yml5
-rw-r--r--changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml5
-rw-r--r--changelogs/unreleased/osw-gitaly-diff-stats-client.yml5
-rw-r--r--changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml5
-rw-r--r--changelogs/unreleased/osw-use-diff-stats-rpc-on-comparison-views.yml5
-rw-r--r--changelogs/unreleased/pedroms-master-patch-57786.yml5
-rw-r--r--changelogs/unreleased/pipeline-event-variables.yml5
-rw-r--r--changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml6
-rw-r--r--changelogs/unreleased/rails5-fix-delete-blob.yml5
-rw-r--r--changelogs/unreleased/rails5-fix-deployment-spec.yml5
-rw-r--r--changelogs/unreleased/rails5-fix-issue-move-service.yml6
-rw-r--r--changelogs/unreleased/remove-sidekiq.yml5
-rw-r--r--changelogs/unreleased/rename-local-variable.yml5
-rw-r--r--changelogs/unreleased/rename-scheduled-label-badges.yml5
-rw-r--r--changelogs/unreleased/rename-squash-before-merge-vue-component.yml5
-rw-r--r--changelogs/unreleased/rouge-3-3-0.yml6
-rw-r--r--changelogs/unreleased/scheduled-manual-jobs.yml5
-rw-r--r--changelogs/unreleased/security-2697-code-highlight-timeout.yml5
-rw-r--r--changelogs/unreleased/security-acet-issue-details.yml5
-rw-r--r--changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml5
-rw-r--r--changelogs/unreleased/security-fix-leaking-private-project-namespace.yml5
-rw-r--r--changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml5
-rw-r--r--changelogs/unreleased/security-osw-user-info-leak-discussions.yml5
-rw-r--r--changelogs/unreleased/security-package-json-xss.yml5
-rw-r--r--changelogs/unreleased/sh-add-audit-logging-json-ce.yml5
-rw-r--r--changelogs/unreleased/sh-allow-key-id-in-params.yml5
-rw-r--r--changelogs/unreleased/sh-associate-rakefile-ruby.yml5
-rw-r--r--changelogs/unreleased/sh-delete-tags-outside-transaction.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-52009.yml5
-rw-r--r--changelogs/unreleased/sh-fix-multipart-upload-signed-urls.yml5
-rw-r--r--changelogs/unreleased/sh-guard-against-ldap-login-csrf-fail.yml5
-rw-r--r--changelogs/unreleased/sh-improve-container-tags-update-username.yml5
-rw-r--r--changelogs/unreleased/sh-pages-eof-error.yml5
-rw-r--r--changelogs/unreleased/sh-strip-github-pat-whitespace.yml5
-rw-r--r--changelogs/unreleased/sh-support-adding-confirmed-emails.yml5
-rw-r--r--changelogs/unreleased/sh-upgrade-katex-0-9-0.yml5
-rw-r--r--changelogs/unreleased/support-license-management-and-performance.yml5
-rw-r--r--changelogs/unreleased/toon-copy-meta-data-fix.yml5
-rw-r--r--changelogs/unreleased/update-operations-metrics-empty-state.yml5
-rw-r--r--changelogs/unreleased/update-readme-ruby-version.yml5
-rw-r--r--changelogs/unreleased/update-runner-chart-to-0-1-34.yml5
-rw-r--r--changelogs/unreleased/update-runner-chart-to-0-1-35.yml5
-rw-r--r--changelogs/unreleased/use-raw-file-format.yml5
-rw-r--r--changelogs/unreleased/vendor-auto-devops-gitlab-ci-fix-503-on-deploy.yml6
-rw-r--r--changelogs/unreleased/vendor-gitlab-ci-auto-devops-yml.yml5
-rw-r--r--changelogs/unreleased/winh-highlight-current-user.yml5
-rw-r--r--changelogs/unreleased/winh-page-title-margin.yml5
-rw-r--r--changelogs/unreleased/zj-render-log-artifacts.yml5
-rw-r--r--config/gitlab.yml.example3
-rw-r--r--config/webpack.config.js2
-rw-r--r--danger/database/Dangerfile4
-rw-r--r--danger/documentation/Dangerfile4
-rw-r--r--danger/eslint/Dangerfile2
-rw-r--r--danger/plugins/helper.rb34
-rw-r--r--danger/prettier/Dangerfile2
-rw-r--r--db/fixtures/development/04_project.rb10
-rw-r--r--db/fixtures/development/14_pipelines.rb75
-rw-r--r--db/migrate/20180925200829_create_user_preferences.rb31
-rw-r--r--db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb24
-rw-r--r--db/post_migrate/20161221153951_rename_reserved_project_names.rb6
-rw-r--r--db/post_migrate/20170313133418_rename_more_reserved_project_names.rb6
-rw-r--r--db/schema.rb31
-rw-r--r--doc/administration/job_artifacts.md12
-rw-r--r--doc/administration/logs.md14
-rw-r--r--doc/administration/pages/index.md27
-rw-r--r--doc/administration/pages/source.md38
-rw-r--r--doc/administration/repository_checks.md2
-rw-r--r--doc/administration/repository_storage_types.md5
-rw-r--r--doc/api/README.md6
-rw-r--r--doc/api/applications.md51
-rw-r--r--doc/api/groups.md7
-rw-r--r--doc/api/notes.md2
-rw-r--r--doc/api/project_templates.md16
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/api/runners.md3
-rw-r--r--doc/api/users.md3
-rw-r--r--doc/ci/docker/using_docker_build.md84
-rw-r--r--doc/ci/docker/using_kaniko.md5
-rw-r--r--doc/ci/examples/test-scala-application.md2
-rw-r--r--doc/ci/runners/README.md2
-rw-r--r--doc/ci/yaml/README.md4
-rw-r--r--doc/development/code_review.md128
-rw-r--r--doc/development/contributing/index.md86
-rw-r--r--doc/development/contributing/issue_workflow.md59
-rw-r--r--doc/development/contributing/merge_request_workflow.md9
-rw-r--r--doc/development/documentation/index.md22
-rw-r--r--doc/development/documentation/structure.md46
-rw-r--r--doc/development/feature_flags.md5
-rw-r--r--doc/development/i18n/proofreader.md5
-rw-r--r--doc/development/testing_guide/best_practices.md155
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/integration/saml.md17
-rw-r--r--doc/update/patch_versions.md2
-rw-r--r--doc/user/group/img/access_requests_management.pngbin11186 -> 22616 bytes
-rw-r--r--doc/user/group/img/add_new_members.pngbin18048 -> 66523 bytes
-rw-r--r--doc/user/group/img/create_new_group_info.pngbin30175 -> 51072 bytes
-rw-r--r--doc/user/group/img/create_new_project_from_group.pngbin3185 -> 37234 bytes
-rw-r--r--doc/user/group/img/group_settings.pngbin9704 -> 51335 bytes
-rw-r--r--doc/user/group/img/request_access_button.pngbin35908 -> 36258 bytes
-rw-r--r--doc/user/group/img/select_group_dropdown.pngbin3482 -> 74893 bytes
-rw-r--r--doc/user/group/img/withdraw_access_request_button.pngbin36393 -> 36782 bytes
-rw-r--r--doc/user/group/index.md7
-rw-r--r--doc/user/profile/account/two_factor_authentication.md12
-rw-r--r--doc/user/profile/personal_access_tokens.md8
-rw-r--r--doc/user/project/clusters/index.md2
-rw-r--r--doc/user/project/container_registry.md13
-rw-r--r--doc/user/project/deploy_tokens/index.md33
-rw-r--r--doc/user/project/issues/issues_functionalities.md9
-rw-r--r--doc/user/project/milestones/index.md3
-rw-r--r--doc/user/project/quick_actions.md1
-rw-r--r--doc/user/project/repository/branches/index.md9
-rw-r--r--doc/workflow/img/repository_mirroring_force_update.pngbin0 -> 45730 bytes
-rw-r--r--doc/workflow/img/repository_mirroring_pull_settings_lower.pngbin0 -> 58056 bytes
-rw-r--r--doc/workflow/img/repository_mirroring_pull_settings_upper.pngbin0 -> 64118 bytes
-rw-r--r--doc/workflow/img/repository_mirroring_push_settings.pngbin0 -> 50526 bytes
-rw-r--r--doc/workflow/repository_mirroring.md551
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.pngbin22952 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.pngbin22668 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_diverged_branch_push.pngbin9509 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_github_edit_personal_access_token.pngbin20739 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.pngbin16538 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.pngbin16765 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.pngbin15555 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.pngbin16414 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_new_project.pngbin20364 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.pngbin40414 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_pull_settings.pngbin39909 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.pngbin26234 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_push_settings.pngbin18226 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.pngbin9343 -> 0 bytes
-rw-r--r--doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.pngbin27440 -> 0 bytes
-rw-r--r--lib/api/applications.rb16
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/features.rb2
-rw-r--r--lib/api/helpers.rb8
-rw-r--r--lib/api/internal.rb4
-rw-r--r--lib/api/jobs.rb2
-rw-r--r--lib/api/users.rb14
-rw-r--r--lib/backup/manager.rb2
-rw-r--r--lib/gitlab/access.rb2
-rw-r--r--lib/gitlab/action_rate_limiter.rb2
-rw-r--r--lib/gitlab/allowable.rb2
-rw-r--r--lib/gitlab/app_logger.rb2
-rw-r--r--lib/gitlab/asciidoc.rb2
-rw-r--r--lib/gitlab/audit_json_logger.rb9
-rw-r--r--lib/gitlab/auth.rb2
-rw-r--r--lib/gitlab/background_migration.rb2
-rw-r--r--lib/gitlab/base_doorkeeper_controller.rb2
-rw-r--r--lib/gitlab/blame.rb2
-rw-r--r--lib/gitlab/blob_helper.rb2
-rw-r--r--lib/gitlab/build_access.rb2
-rw-r--r--lib/gitlab/changes_list.rb2
-rw-r--r--lib/gitlab/chat_name_token.rb2
-rw-r--r--lib/gitlab/ci/build/artifacts/adapters/gzip_stream.rb48
-rw-r--r--lib/gitlab/ci/build/artifacts/adapters/raw_stream.rb27
-rw-r--r--lib/gitlab/ci/build/artifacts/gzip_file_adapter.rb46
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb7
-rw-r--r--lib/gitlab/ci/config.rb8
-rw-r--r--lib/gitlab/ci/config/entry/reports.rb4
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb75
-rw-r--r--lib/gitlab/ci/config/external/file/local.rb42
-rw-r--r--lib/gitlab/ci/config/external/file/remote.rb49
-rw-r--r--lib/gitlab/ci/config/external/mapper.rb33
-rw-r--r--lib/gitlab/ci/config/external/processor.rb50
-rw-r--r--lib/gitlab/ci/external/file/base.rb29
-rw-r--r--lib/gitlab/ci/external/file/local.rb34
-rw-r--r--lib/gitlab/ci/external/file/remote.rb30
-rw-r--r--lib/gitlab/ci/external/mapper.rb32
-rw-r--r--lib/gitlab/ci/external/processor.rb52
-rw-r--r--lib/gitlab/ci/status/build/scheduled.rb6
-rw-r--r--lib/gitlab/ci/status/pipeline/delayed.rb (renamed from lib/gitlab/ci/status/pipeline/scheduled.rb)4
-rw-r--r--lib/gitlab/ci/status/pipeline/factory.rb2
-rw-r--r--lib/gitlab/ci/status/scheduled.rb4
-rw-r--r--lib/gitlab/ci_access.rb2
-rw-r--r--lib/gitlab/closing_issue_extractor.rb2
-rw-r--r--lib/gitlab/color_schemes.rb2
-rw-r--r--lib/gitlab/config_helper.rb2
-rw-r--r--lib/gitlab/contributions_calendar.rb2
-rw-r--r--lib/gitlab/contributor.rb2
-rw-r--r--lib/gitlab/cross_project_access.rb2
-rw-r--r--lib/gitlab/current_settings.rb2
-rw-r--r--lib/gitlab/daemon.rb2
-rw-r--r--lib/gitlab/database.rb12
-rw-r--r--lib/gitlab/database/migration_helpers.rb2
-rw-r--r--lib/gitlab/dependency_linker.rb2
-rw-r--r--lib/gitlab/downtime_check.rb2
-rw-r--r--lib/gitlab/ee_compat_check.rb12
-rw-r--r--lib/gitlab/emoji.rb2
-rw-r--r--lib/gitlab/encoding_helper.rb2
-rw-r--r--lib/gitlab/environment.rb2
-rw-r--r--lib/gitlab/environment_logger.rb2
-rw-r--r--lib/gitlab/exclusive_lease.rb2
-rw-r--r--lib/gitlab/exclusive_lease_helpers.rb2
-rw-r--r--lib/gitlab/fake_application_settings.rb2
-rw-r--r--lib/gitlab/favicon.rb2
-rw-r--r--lib/gitlab/file_detector.rb2
-rw-r--r--lib/gitlab/file_finder.rb2
-rw-r--r--lib/gitlab/file_markdown_link_builder.rb4
-rw-r--r--lib/gitlab/git.rb2
-rw-r--r--lib/gitlab/git_access.rb2
-rw-r--r--lib/gitlab/git_access_wiki.rb2
-rw-r--r--lib/gitlab/git_logger.rb2
-rw-r--r--lib/gitlab/git_ref_validator.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb4
-rw-r--r--lib/gitlab/github_import.rb2
-rw-r--r--lib/gitlab/gl_id.rb2
-rw-r--r--lib/gitlab/gl_repository.rb2
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--lib/gitlab/google_code_import/importer.rb2
-rw-r--r--lib/gitlab/gpg.rb2
-rw-r--r--lib/gitlab/graphql.rb2
-rw-r--r--lib/gitlab/group_hierarchy.rb2
-rw-r--r--lib/gitlab/highlight.rb2
-rw-r--r--lib/gitlab/http.rb9
-rw-r--r--lib/gitlab/http_io.rb15
-rw-r--r--lib/gitlab/i18n.rb2
-rw-r--r--lib/gitlab/identifier.rb2
-rw-r--r--lib/gitlab/import_export.rb2
-rw-r--r--lib/gitlab/import_formatter.rb2
-rw-r--r--lib/gitlab/import_sources.rb2
-rw-r--r--lib/gitlab/incoming_email.rb2
-rw-r--r--lib/gitlab/insecure_key_fingerprint.rb2
-rw-r--r--lib/gitlab/issuable_metadata.rb2
-rw-r--r--lib/gitlab/issuable_sorter.rb2
-rw-r--r--lib/gitlab/issuables_count_for_state.rb2
-rw-r--r--lib/gitlab/issues_labels.rb2
-rw-r--r--lib/gitlab/job_waiter.rb2
-rw-r--r--lib/gitlab/json_logger.rb2
-rw-r--r--lib/gitlab/kubernetes.rb2
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb7
-rw-r--r--lib/gitlab/kubernetes/role_binding.rb47
-rw-r--r--lib/gitlab/language_detection.rb2
-rw-r--r--lib/gitlab/lazy.rb2
-rw-r--r--lib/gitlab/lfs_token.rb2
-rw-r--r--lib/gitlab/logger.rb2
-rw-r--r--lib/gitlab/mail_room.rb2
-rw-r--r--lib/gitlab/markup_helper.rb2
-rw-r--r--lib/gitlab/metrics.rb2
-rw-r--r--lib/gitlab/metrics/influx_db.rb2
-rw-r--r--lib/gitlab/multi_collection_paginator.rb2
-rw-r--r--lib/gitlab/namespace_sanitizer.rb9
-rw-r--r--lib/gitlab/omniauth_initializer.rb2
-rw-r--r--lib/gitlab/optimistic_locking.rb2
-rw-r--r--lib/gitlab/other_markup.rb2
-rw-r--r--lib/gitlab/otp_key_rotator.rb2
-rw-r--r--lib/gitlab/pages.rb2
-rw-r--r--lib/gitlab/pages_client.rb2
-rw-r--r--lib/gitlab/pages_transfer.rb2
-rw-r--r--lib/gitlab/path_regex.rb2
-rw-r--r--lib/gitlab/performance_bar.rb2
-rw-r--r--lib/gitlab/plugin.rb2
-rw-r--r--lib/gitlab/plugin_logger.rb2
-rw-r--r--lib/gitlab/polling_interval.rb2
-rw-r--r--lib/gitlab/popen.rb4
-rw-r--r--lib/gitlab/profiler.rb2
-rw-r--r--lib/gitlab/project_search_results.rb7
-rw-r--r--lib/gitlab/project_service_logger.rb2
-rw-r--r--lib/gitlab/project_template.rb2
-rw-r--r--lib/gitlab/project_transfer.rb2
-rw-r--r--lib/gitlab/prometheus_client.rb2
-rw-r--r--lib/gitlab/protocol_access.rb2
-rw-r--r--lib/gitlab/proxy_http_connection_adapter.rb2
-rw-r--r--lib/gitlab/query_limiting.rb2
-rw-r--r--lib/gitlab/recaptcha.rb2
-rw-r--r--lib/gitlab/reference_counter.rb2
-rw-r--r--lib/gitlab/reference_extractor.rb2
-rw-r--r--lib/gitlab/regex.rb2
-rw-r--r--lib/gitlab/repo_path.rb2
-rw-r--r--lib/gitlab/repository_cache.rb4
-rw-r--r--lib/gitlab/repository_cache_adapter.rb2
-rw-r--r--lib/gitlab/repository_check_logger.rb2
-rw-r--r--lib/gitlab/request_context.rb2
-rw-r--r--lib/gitlab/request_forgery_protection.rb2
-rw-r--r--lib/gitlab/request_profiler.rb2
-rw-r--r--lib/gitlab/route_map.rb2
-rw-r--r--lib/gitlab/routing.rb4
-rw-r--r--lib/gitlab/search_results.rb2
-rw-r--r--lib/gitlab/seeder.rb2
-rw-r--r--lib/gitlab/sentry.rb2
-rw-r--r--lib/gitlab/setup_helper.rb7
-rw-r--r--lib/gitlab/shard_health_cache.rb2
-rw-r--r--lib/gitlab/shell.rb2
-rw-r--r--lib/gitlab/shell_adapter.rb2
-rw-r--r--lib/gitlab/sherlock.rb2
-rw-r--r--lib/gitlab/sidekiq_config.rb2
-rw-r--r--lib/gitlab/sidekiq_logger.rb2
-rw-r--r--lib/gitlab/sidekiq_status.rb2
-rw-r--r--lib/gitlab/sidekiq_versioning.rb2
-rw-r--r--lib/gitlab/snippet_search_results.rb2
-rw-r--r--lib/gitlab/ssh_public_key.rb4
-rw-r--r--lib/gitlab/string_placeholder_replacer.rb2
-rw-r--r--lib/gitlab/string_range_marker.rb2
-rw-r--r--lib/gitlab/string_regex_marker.rb2
-rw-r--r--lib/gitlab/task_helpers.rb4
-rw-r--r--lib/gitlab/tcp_checker.rb2
-rw-r--r--lib/gitlab/template/base_template.rb2
-rw-r--r--lib/gitlab/template_helper.rb2
-rw-r--r--lib/gitlab/temporarily_allow.rb2
-rw-r--r--lib/gitlab/themes.rb2
-rw-r--r--lib/gitlab/time_tracking_formatter.rb2
-rw-r--r--lib/gitlab/timeless.rb2
-rw-r--r--lib/gitlab/tree_summary.rb2
-rw-r--r--lib/gitlab/untrusted_regexp.rb2
-rw-r--r--lib/gitlab/update_path_error.rb2
-rw-r--r--lib/gitlab/upgrader.rb2
-rw-r--r--lib/gitlab/uploads_transfer.rb2
-rw-r--r--lib/gitlab/url_blocker.rb2
-rw-r--r--lib/gitlab/url_builder.rb2
-rw-r--r--lib/gitlab/url_sanitizer.rb2
-rw-r--r--lib/gitlab/usage_data.rb2
-rw-r--r--lib/gitlab/user_access.rb2
-rw-r--r--lib/gitlab/utils.rb2
-rw-r--r--lib/gitlab/version_info.rb2
-rw-r--r--lib/gitlab/visibility_level.rb2
-rw-r--r--lib/gitlab/wiki_file_finder.rb2
-rw-r--r--lib/gitlab/workhorse.rb4
-rw-r--r--lib/haml_lint/inline_javascript.rb6
-rw-r--r--lib/quality/helm_client.rb88
-rw-r--r--lib/quality/kubernetes_client.rb28
-rw-r--r--lib/tasks/import.rake2
-rw-r--r--locale/gitlab.pot52
-rw-r--r--package.json7
-rw-r--r--qa/qa.rb9
-rw-r--r--qa/qa/factory/README.md12
-rw-r--r--qa/qa/factory/base.rb2
-rw-r--r--qa/qa/factory/repository/push.rb12
-rw-r--r--qa/qa/factory/resource/merge_request.rb1
-rw-r--r--qa/qa/factory/settings/hashed_storage.rb2
-rw-r--r--qa/qa/git/repository.rb140
-rw-r--r--qa/qa/page/base.rb1
-rw-r--r--qa/qa/page/project/job/show.rb25
-rw-r--r--qa/qa/page/project/menu.rb14
-rw-r--r--qa/qa/page/project/show.rb10
-rw-r--r--qa/qa/runtime/env.rb10
-rw-r--r--qa/qa/runtime/logger.rb23
-rw-r--r--qa/qa/specs/features/api/1_manage/users_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb2
-rw-r--r--qa/qa/support/page/logging.rb100
-rw-r--r--qa/spec/factory/base_spec.rb12
-rw-r--r--qa/spec/factory/repository/push_spec.rb26
-rw-r--r--qa/spec/git/repository_spec.rb15
-rw-r--r--qa/spec/page/logging_spec.rb88
-rw-r--r--qa/spec/runtime/env_spec.rb24
-rw-r--r--qa/spec/runtime/logger_spec.rb27
-rw-r--r--qa/spec/spec_helper.rb4
-rw-r--r--scripts/frontend/frontend_script_utils.js3
-rw-r--r--scripts/frontend/prettier.js206
-rwxr-xr-xscripts/review_apps/automated_cleanup.rb102
-rwxr-xr-xscripts/review_apps/review-apps.sh37
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb49
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb7
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb18
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb31
-rw-r--r--spec/factories/ci/job_artifacts.rb4
-rw-r--r--spec/factories/ci/runners.rb10
-rw-r--r--spec/factories/clusters/kubernetes_namespaces.rb9
-rw-r--r--spec/factories/clusters/projects.rb8
-rw-r--r--spec/factories/deployments.rb5
-rw-r--r--spec/factories/forked_project_links.rb15
-rw-r--r--spec/factories/merge_requests.rb14
-rw-r--r--spec/factories/user_preferences.rb12
-rw-r--r--spec/features/admin/dashboard_spec.rb28
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb8
-rw-r--r--spec/features/groups/milestone_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb10
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb6
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb4
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb25
-rw-r--r--spec/features/merge_request/user_edits_mr_spec.rb4
-rw-r--r--spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb8
-rw-r--r--spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb8
-rw-r--r--spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb8
-rw-r--r--spec/features/merge_request/user_views_open_merge_request_spec.rb6
-rw-r--r--spec/features/projects/commit/comments/user_adds_comment_spec.rb10
-rw-r--r--spec/features/projects/commit/user_comments_on_commit_spec.rb6
-rw-r--r--spec/features/projects/jobs_spec.rb43
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb18
-rw-r--r--spec/finders/applications_finder_spec.rb22
-rw-r--r--spec/finders/fork_projects_finder_spec.rb15
-rw-r--r--spec/finders/notes_finder_spec.rb21
-rw-r--r--spec/finders/user_finder_spec.rb154
-rw-r--r--spec/finders/users_finder_spec.rb6
-rw-r--r--spec/fixtures/security-reports/feature-branch.zipbin0 -> 7163 bytes
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json18
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-dast-report.json40
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json46
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-license-management-report.json242
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-sast-report.json944
-rw-r--r--spec/fixtures/security-reports/master.zipbin0 -> 6710 bytes
-rw-r--r--spec/fixtures/security-reports/master/gl-container-scanning-report.json18
-rw-r--r--spec/fixtures/security-reports/master/gl-dast-report.json40
-rw-r--r--spec/fixtures/security-reports/master/gl-dependency-scanning-report.json35
-rw-r--r--spec/fixtures/security-reports/master/gl-license-management-report.json150
-rw-r--r--spec/fixtures/security-reports/master/gl-sast-report.json944
-rw-r--r--spec/helpers/time_helper_spec.rb35
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb12
-rw-r--r--spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js2
-rw-r--r--spec/javascripts/collapsed_sidebar_todo_spec.js12
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js72
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js22
-rw-r--r--spec/javascripts/flash_spec.js4
-rw-r--r--spec/javascripts/ide/components/new_dropdown/upload_spec.js40
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js2
-rw-r--r--spec/javascripts/image_diff/image_diff_spec.js10
-rw-r--r--spec/javascripts/jobs/components/job_container_item_spec.js73
-rw-r--r--spec/javascripts/jobs/mock_data.js4
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js8
-rw-r--r--spec/javascripts/notes/components/discussion_filter_spec.js60
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js3
-rw-r--r--spec/javascripts/notes/mock_data.js15
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js52
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js94
-rw-r--r--spec/javascripts/vue_shared/components/bar_chart_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/ci_badge_link_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js36
-rw-r--r--spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js91
-rw-r--r--spec/javascripts/vue_shared/components/markdown/header_spec.js2
-rw-r--r--spec/lib/gitaly/server_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb (renamed from spec/lib/gitlab/ci/build/artifacts/gzip_file_adapter_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb47
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb (renamed from spec/lib/gitlab/ci/external/file/local_spec.rb)4
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb (renamed from spec/lib/gitlab/ci/external/file/remote_spec.rb)51
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb (renamed from spec/lib/gitlab/ci/external/mapper_spec.rb)8
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb (renamed from spec/lib/gitlab/ci/external/processor_spec.rb)26
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/scheduled_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb (renamed from spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb)4
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/scheduled_spec.rb4
-rw-r--r--spec/lib/gitlab/http_spec.rb26
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/role_binding_spec.rb47
-rw-r--r--spec/lib/quality/helm_client_spec.rb105
-rw-r--r--spec/lib/quality/kubernetes_client_spec.rb34
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb11
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb11
-rw-r--r--spec/models/ci/job_artifact_spec.rb8
-rw-r--r--spec/models/ci/pipeline_spec.rb35
-rw-r--r--spec/models/clusters/applications/runner_spec.rb6
-rw-r--r--spec/models/clusters/cluster_spec.rb4
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb84
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb75
-rw-r--r--spec/models/clusters/project_spec.rb2
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb34
-rw-r--r--spec/models/deployment_spec.rb2
-rw-r--r--spec/models/environment_status_spec.rb61
-rw-r--r--spec/models/forked_project_link_spec.rb68
-rw-r--r--spec/models/note_spec.rb24
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb3
-rw-r--r--spec/models/project_spec.rb190
-rw-r--r--spec/models/user_preference_spec.rb32
-rw-r--r--spec/models/user_spec.rb9
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb32
-rw-r--r--spec/requests/api/applications_spec.rb70
-rw-r--r--spec/requests/api/helpers_spec.rb16
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb43
-rw-r--r--spec/serializers/environment_status_entity_spec.rb49
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb8
-rw-r--r--spec/services/application_settings/update_service_spec.rb3
-rw-r--r--spec/services/audit_event_service_spec.rb26
-rw-r--r--spec/services/ci/process_build_service_spec.rb232
-rw-r--r--spec/services/ci/retry_build_service_spec.rb1
-rw-r--r--spec/services/groups/update_service_spec.rb6
-rw-r--r--spec/services/projects/after_rename_service_spec.rb198
-rw-r--r--spec/services/projects/create_service_spec.rb11
-rw-r--r--spec/services/projects/destroy_service_spec.rb1
-rw-r--r--spec/services/projects/fork_service_spec.rb25
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb9
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb79
-rw-r--r--spec/services/web_hook_service_spec.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb12
-rw-r--r--spec/support/helpers/ci_artifact_metadata_generator.rb48
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb23
-rw-r--r--spec/support/helpers/project_forks_helper.rb2
-rw-r--r--spec/support/helpers/test_env.rb13
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb54
-rw-r--r--spec/support/stored_repositories.rb4
-rw-r--r--spec/views/shared/runners/show.html.haml_spec.rb155
-rw-r--r--spec/workers/namespaceless_project_destroy_worker_spec.rb4
-rw-r--r--spec/workers/rebase_worker_spec.rb12
-rw-r--r--spec/workers/repository_check/batch_worker_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb32
-rw-r--r--yarn.lock165
893 files changed, 10495 insertions, 3695 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c652b6c75e2..ccc9e640970 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -139,7 +139,7 @@ stages:
- export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}"
- apk add --update openssl
- wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME
- - chmod 755 $SCRIPT_NAME
+ - chmod 755 $(basename $SCRIPT_NAME)
.rake-exec: &rake-exec
<<: *dedicated-no-docs-no-db-pull-cache-job
@@ -723,7 +723,7 @@ gitlab:assets:compile:
- public/assets/
karma:
- <<: *dedicated-no-docs-and-no-qa-pull-cache-job
+ <<: *dedicated-no-docs-pull-cache-job
<<: *use-pg
dependencies:
- compile-assets
@@ -929,3 +929,94 @@ no_ee_check:
- scripts/no-ee-check
only:
- //@gitlab-org/gitlab-ce
+
+# GitLab Review apps
+review:
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
+ stage: test
+ allow_failure: true
+ before_script:
+ - gem install gitlab --no-document
+ variables:
+ GIT_DEPTH: "1"
+ HOST_SUFFIX: "$CI_ENVIRONMENT_SLUG"
+ DOMAIN: "-$CI_ENVIRONMENT_SLUG.$REVIEW_APPS_DOMAIN"
+ GITLAB_HELM_CHART_REF: "master"
+ script:
+ - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
+ - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
+ - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
+ - source ./scripts/review_apps/review-apps.sh
+ - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
+ - check_kube_domain
+ - download_gitlab_chart
+ - ensure_namespace
+ - install_tiller
+ - create_secret
+ - install_external_dns
+ - deploy
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ url: https://gitlab-$CI_ENVIRONMENT_SLUG.$REVIEW_APPS_DOMAIN
+ on_stop: stop_review
+ only:
+ refs:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
+ kubernetes: active
+ except:
+ refs:
+ - master
+ - /(^docs[\/-].*|.*-docs$)/
+
+stop_review:
+ <<: *single-script-job
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
+ stage: test
+ allow_failure: true
+ cache: {}
+ dependencies: []
+ variables:
+ SCRIPT_NAME: "review_apps/review-apps.sh"
+ script:
+ - source $(basename "${SCRIPT_NAME}")
+ - delete
+ - cleanup
+ when: manual
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ action: stop
+ only:
+ refs:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
+ kubernetes: active
+ except:
+ - master
+ - /(^docs[\/-].*|.*-docs$)/
+
+schedule:review_apps_cleanup:
+ <<: *dedicated-no-docs-pull-cache-job
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
+ stage: build
+ allow_failure: true
+ cache: {}
+ dependencies: []
+ before_script:
+ - gem install gitlab --no-document
+ variables:
+ GIT_DEPTH: "1"
+ script:
+ - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
+ environment:
+ name: review/auto-cleanup
+ action: stop
+ only:
+ refs:
+ - schedules@gitlab-org/gitlab-ce
+ - schedules@gitlab-org/gitlab-ee
+ kubernetes: active
+ except:
+ - master
+ - tags
+ - /(^docs[\/-].*|.*-docs$)/
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index 64b54b171f7..69cf7fe1548 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -16,7 +16,6 @@ Set the title to: `[Security] Description of the original issue`
- [ ] Add a link to the MR to the [links section](#links)
- [ ] Add a link to an EE MR if required
- [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**.
-- [ ] Assign the MR to a RM once is reviewed and ready to be merged. Check the [RM list] to see who to ping.
#### Backports
@@ -26,7 +25,8 @@ Set the title to: `[Security] Description of the original issue`
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
- [ ] Create each MR targetting the security branch `security-X-Y`
- [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR
-- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager.
+- [ ] Add the ~"Merge into Security" label to all of the MRs.
+- [ ] Make sure all MRs have a link in the [links section](#links)
[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script
diff --git a/.gitlab/issue_templates/Test plan.md b/.gitlab/issue_templates/Test plan.md
index 580fab206b3..db8e30c419c 100644
--- a/.gitlab/issue_templates/Test plan.md
+++ b/.gitlab/issue_templates/Test plan.md
@@ -38,22 +38,22 @@ test plan](https://testing.googleblog.com/2011/09/10-minute-test-plan.html) and
[this wiki page from an open-source tool that implements the ACC
model](https://code.google.com/archive/p/test-analytics/wikis/AccExplained.wiki). -->
-| | Simple | Secure | Responsive | Obvious | Stable |
-|------------|:------:|:------:|:----------:|:-------:|:------:|
-| Admin | | | | | |
-| Groups | | | | | |
-| Project | | | | | |
-| Repository | | | | | |
-| Issues | | | | | |
-| MRs | | | | | |
-| CI/CD | | | | | |
-| Ops | | | | | |
-| Registry | | | | | |
-| Wiki | | | | | |
-| Snippets | | | | | |
-| Settings | | | | | |
-| Tracking | | | | | |
-| API | | | | | |
+| | Secure | Responsive | Intuitive | Reliable |
+|------------|:------:|:----------:|:---------:|:--------:|
+| Admin | | | | |
+| Groups | | | | |
+| Project | | | | |
+| Repository | | | | |
+| Issues | | | | |
+| MRs | | | | |
+| CI/CD | | | | |
+| Ops | | | | |
+| Registry | | | | |
+| Wiki | | | | |
+| Snippets | | | | |
+| Settings | | | | |
+| Tracking | | | | |
+| API | | | | |
## Capabilities
@@ -65,7 +65,7 @@ more complex features could involve multiple or even all.
Example (from https://gitlab.com/gitlab-org/gitlab-ce/issues/50353):
* Respository is
- * Simple
+ * Intuitive
* It's easy to select the desired file template
* It doesn't require unnecessary actions to save the change
* It's easy to undo the change after selecting a template
@@ -93,4 +93,4 @@ When adding new automated tests, please keep [testing levels](https://docs.gitla
in mind.
-->
-/label ~Quality \ No newline at end of file
+/label ~Quality ~"test plan" \ No newline at end of file
diff --git a/.gitlab/merge_request_templates/Database changes.md b/.gitlab/merge_request_templates/Database changes.md
index e636ec313df..354393b60e0 100644
--- a/.gitlab/merge_request_templates/Database changes.md
+++ b/.gitlab/merge_request_templates/Database changes.md
@@ -1,8 +1,23 @@
-Add a description of your merge request here. Merge requests without an adequate
-description will not be reviewed until one is added.
+## What does this MR do?
+
+<!--
+Describe in detail what your merge request does, why it does that, etc. Merge
+requests without an adequate description will not be reviewed until one is
+added.
+
+Please also keep this description up-to-date with any discussion that takes
+place so that reviewers can understand your intent. This is especially
+important if they didn't participate in the discussion.
+
+Make sure to remove this comment when you are done.
+-->
+
+Add a description of your merge request here.
## Database checklist
+- [ ] Conforms to the [database guides](https://docs.gitlab.com/ee/development/README.html#databases-guides)
+
When adding migrations:
- [ ] Updated `db/schema.rb`
@@ -35,16 +50,9 @@ When removing columns, tables, indexes or other structures:
- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/index.html#contributing-to-docs)
-- [ ] [API support added](https://docs.gitlab.com/ee/development/api_styleguide.html)
- [ ] [Tests added for this feature/bug](https://docs.gitlab.com/ee/development/testing_guide/index.html)
-- Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
- - [ ] Has been reviewed by a Backend [maintainer](https://about.gitlab.com/handbook/engineering/#maintainer)
- - [ ] Has been reviewed by a Database [specialist](https://about.gitlab.com/team/structure/#specialist)
+- [ ] Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
- [ ] Conforms to the [merge request performance guidelines](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
- [ ] Conforms to the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
-- [ ] If you have multiple commits, please combine them into a few logically organized commits by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
-- [ ] [Internationalization required/considered](https://docs.gitlab.com/ee/development/i18n/index.html)
-- [ ] For a paid feature, have we considered GitLab.com plans, how it works for groups, and is there a design for promoting it to users who aren't on the correct plan?
-- [ ] [End-to-end tests](https://docs.gitlab.com/ee/development/testing_guide/end_to_end_tests.html#testing-code-in-merge-requests) pass (`package-and-qa` manual pipeline job)
/label ~database
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
index ca38c881c66..8b7e7119790 100644
--- a/.gitlab/merge_request_templates/Documentation.md
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -19,6 +19,7 @@ Closes
- [ ] [Apply the correct labels and milestone](https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process)
- [ ] Crosslink the document from the higher-level index
- [ ] Crosslink the document from other subject-related docs
+- [ ] Feature moving tiers? Make sure the change is also reflected in [`features.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
- [ ] Correctly apply the product [badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) and [tiers](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers)
- [ ] [Port the MR to EE (or backport from CE)](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee): _always recommended, required when the `ee-compat-check` job fails_
diff --git a/.prettierignore b/.prettierignore
index b674ccd50cf..dc9e572ab54 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -3,3 +3,7 @@
/public/
/vendor/
/tmp/
+
+# ignore stylesheets for now as this clashes with our linter
+*.css
+*.scss
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2667c8a2fe1..2fc5b24aa39 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,231 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.4.0 (2018-10-22)
+
+### Security (9 changes)
+
+- Filter user sensitive data from discussions JSON. !2536
+- Encrypt webhook tokens and URLs in the database. !21645
+- Redact confidential events in the API.
+- Set timeout for syntax highlighting.
+- Sanitize JSON data properly to fix XSS on Issue details page.
+- Markdown API no longer displays confidential title references unless authorized.
+- Properly filter private references from system notes.
+- Fix stored XSS in merge requests from imported repository.
+- Fix xss vulnerability sourced from package.json.
+
+### Removed (2 changes)
+
+- Remove background job throttling feature. !21748
+- Remove sidekiq info from performance bar.
+
+### Fixed (68 changes, 18 of them are from the community)
+
+- Fixes 500 for cherry pick API with empty branch name. !21501 (Jacopo Beschi @jacopo-beschi)
+- Fix sorting by priority or popularity on group issues page, when also searching issue content. !21521
+- Fix vertical alignment of text in diffs. !21573
+- Fix performance bar modal position. !21577
+- Bump KaTeX version to 0.9.0. !21625
+- Correctly show legacy diff notes in the merge request changes tab. !21652
+- Synchronize the default branch when updating a remote mirror. !21653
+- Filter group milestones based on user membership. !21660
+- Fix double title in merge request chat messages. !21670 (Kukovskii Vladimir)
+- Delete container repository tags outside of transaction. !21679
+- Images are no longer displayed in Todo descriptions. !21704
+- Fixed merge request widget discussion state not updating after resolving discussions. !21705
+- Vendor Auto-DevOps.gitlab-ci.yml to fix bug where the deploy job does not wait for Deployment to complete. !21713
+- Use Reliable Sidekiq fetch. !21715
+- No longer show open issues from archived projects in group issue board. !21721
+- Issue and MR count now ignores archived projects. !21721
+- Fix resizing of monitoring dashboard. !21730
+- Fix object storage uploads not working with AWS v2. !21731
+- Don't ignore first action when assign and unassign quick actions are used in the same comment. !21749
+- Align form labels following Bootstrap 4 docs. !21752
+- Respect the user commit email in more places. !21773
+- Use stats RPC when comparing diffs. !21778
+- Show commit details for selected commit in MR diffs. !21784
+- Resolve "Geo: Does not mark repositories as missing on primary due to stale cache". !21789
+- Fix leading slash in redirects and add rubocop cop. !21828 (Sanad Liaquat)
+- Fix activity titles for MRs in chat notification services. !21834
+- Hides Close Merge request btn on merged Merge request. !21840 (Jacopo Beschi @jacopo-beschi)
+- Doesn't synchronize the default branch for push mirrors. !21861
+- Fix broken styling when issue board is collapsed. !21868 (Andrea Leone)
+- Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse. !21870 (David Piegza)
+- Fix resolved discussions being unresolved when commented on. !21881
+- Fix timeout when running the RemoveRestrictedTodos background migration. !21893
+- Enable the ability to use the force env for rebuilding authorized_keys during a restore. !21896
+- Fix link handling for issue cards to avoid too sensitive drag events. !21910 (Johann Hubert Sonntagbauer)
+- Guard against a login attempt with invalid CSRF token. !21934
+- Allow setting user's organization and location attributes through the API by adding them to the list of allowed parameters. !21938 (Alexis Reigel)
+- Includes commit stats in POST project commits API. !21968 (Jacopo Beschi @jacopo-beschi)
+- Fix loading issue on some merge request discussion. !21982
+- Prevent Error 500s with invalid relative links. !22001
+- Fix stale issue boards after browser back. !22006 (Johann Hubert Sonntagbauer)
+- Filter issues without an Assignee via the API. !22009 (Eva Kadlecová)
+- Fixes modal button alignment. !22024 (Jacopo Beschi @jacopo-beschi)
+- Fix rendering placeholder notes. !22078
+- Instance Configuration page now displays correct SSH fingerprints. !22081
+- Fix showing diff file header for renamed files. !22089
+- Fix LFS uploaded images not being rendered. !22092
+- Fix the issue where long environment names aren't being truncated, causing the environment name to overlap into the column next to it. !22104
+- Trim whitespace when inviting a new user by email. !22119 (Jacopo Beschi @jacopo-beschi)
+- Fix incorrect parent path on group settings page. !22142
+- Update copy to clipboard button data for application secret. !22268 (George Tsiolis)
+- Improve MR file tree in smaller screens. !22273
+- Fix project deletion when there is a export available. !22276
+- Fixes stuck block URL linking to documentation instead of settings page. !22286
+- Fix caching issue with pipelines URL. !22293
+- Fix erased block not being rendered when job was erased. !22294
+- Load correct stage in the stages dropdown. !22317
+- Fixes close/reopen quick actions preview for issues and merge_requests. !22343 (Jacopo Beschi @jacopo-beschi)
+- Allow Issue and Merge Request sidebar to be toggled from collapsed state. !22353
+- Fix filter bar height bug when a tag is added.
+- Fix the state of the Done button when there is an error in the GitLab Todos section. (marcos8896)
+- Fix wrong text color of help text in merge request creation. (Gerard Montemayor)
+- Add borders and white background to markdown tables.
+- Fixed mention autocomplete in edit merge request.
+- Fix long webhook URL overflow for custom integration. (Kukovskii Vladimir)
+- Fixed file templates not fully being fetched in Web IDE.
+- Fixes performance bar looking for a key in a undefined prop.
+- Hides sidebar for job page in mobile.
+- Fixes triggered/created labeled in job header.
+
+### Changed (26 changes, 4 of them are from the community)
+
+- Enable unauthenticated access to public SSH keys via the API. !20118 (Ronald Claveau)
+- Support Kubernetes RBAC for GitLab Managed Apps when creating new clusters. !21401
+- Highlight current user in comments. !21406
+- Excludes project marked from deletion to projects API. !21542 (Jacopo Beschi @jacopo-beschi)
+- Improve install flow of Kubernetes cluster apps. !21567
+- Move including external files in .gitlab-ci.yml from Starter to Libre. !21603
+- Simplify runner registration token resetting. !21658
+- Filter any parameters ending with "key" in logs. !21688
+- Ensure the schema is loaded with post_migrations included. !21689
+- Updated icons used in filtered search dropdowns. !21694
+- Enable omniauth by default. !21700
+- Vendor Auto-DevOps.gitlab-ci.yml to refactor registry_login. !21714 (Laurent Goderre @LaurentGoderre)
+- Add Gitaly diff stats RPC client. !21732
+- Allow user to revoke an authorized application even if User OAuth applications setting is disabled in admin settings. !21835
+- Change vertical margin of page titles to 16px. !21888
+- Preserve order of project tags list. !21897
+- Avoid close icon leaving the modal header. !21904
+- Allow /copy_metadata for new issues and MRs. !21953
+- Link to the tag for a version on the help page instead of to the commit. !22015
+- Show SHA for pre-release versions on the help page. !22026
+- Use local tiller for Auto DevOps. !22036
+- Remove 'rbac_clusters' feature flag. !22096
+- Increased retained event data by extending events pruner timeframe to 2 years. !22145
+- Add installation type to backup information file. !22150
+- Remove duplicate button from the markdown header toolbar. !22192 (George Tsiolis)
+- Update to Rouge 3.3.0 including frozen string literals for improved memory usage.
+
+### Performance (17 changes, 6 of them are from the community)
+
+- Enable frozen string in app/controllers/**/*.rb.
+- Improve lazy image loading performance by using IntersectionObserver where available. !21565
+- Adds support for Gitaly ListLastCommitsForTree RPC in order to make bulk-fetch of commits more performant. !21921
+- Dont create license_management build when not included in license. !21958
+- Skip creating auto devops jobs for sast, container_scanning, dast, dependency_scanning when not licensed. !21959
+- Reduce queries needed to compute notification recipients. !22050
+- Banzai label ref finder - minimize SQL calls by sharing context more aggresively. !22070
+- Removes expensive dead code on main MR page request. !22153
+- Lazy load xterm custom colors css.
+- Mitigate N+1 queries when parsing commit references in comments.
+- Enable more frozen string in app/controllers/. (gfyoung)
+- Increase performance when creating discussions on diff.
+- Enable frozen string in lib/api and lib/backup. (gfyoung)
+- Enable frozen string in vestigial files. (gfyoung)
+- Enable frozen string for app/helpers/**/*.rb. (gfyoung)
+- Enable frozen string in app/graphql + app/finders. (gfyoung)
+- Enable even more frozen string in app/controllers. (gfyoung)
+
+### Added (37 changes, 21 of them are from the community)
+
+- Allow file templates to be requested at the project level. !7776
+- Add /lock and /unlock quick actions. !15197 (Mehdi Lahmam (@mehlah))
+- Added search functionality for Work In Progress (WIP) merge requests. !18119 (Chantal Rollison)
+- pipeline webhook event now contain pipeline variables. !18171 (Pierre Tardy)
+- Add markdown header toolbar button to insert table. !18480 (George Tsiolis)
+- Add link button to markdown editor toolbar. !18579 (Jan Beckmann)
+- Add access control to GitLab pages and make it possible to enable/disable it in project settings. !18589 (Tuomo Ala-Vannesluoma)
+- Add a filter bar to the admin runners view and add a state filter. !19625 (Alexis Reigel)
+- Add a type filter to the admin runners view. !19649 (Alexis Reigel)
+- Allow user to choose the email used for commits made through GitLab's UI. !21213 (Joshua Campbell)
+- Add autocomplete drop down filter for project snippets. !21458 (Fabian Schneider)
+- Allow events filter to be set in the URL in addition to cookie. !21557 (Igor @igas)
+- Adds a initialize_with_readme parameter to POST /projects. !21617 (Steve)
+- Add ability to skip user email confirmation with API. !21630
+- Add sorting for labels on labels page. !21642
+- Set user status from within user menu. !21643
+- Copy nurtch demo notebooks at Jupyter startup. !21698 (Amit Rathi)
+- Allows to sort projects by most stars. !21762 (Jacopo Beschi @jacopo-beschi)
+- Allow pipelines to schedule delayed job runs. !21767
+- Added tree of changed files to merge request diffs. !21833
+- Add GitLab version components to CI environment variables. !21853
+- Allows to chmod file with commits API. !21866 (Jacopo Beschi @jacopo-beschi)
+- Make single diff patch limit configurable. !21886
+- Extend reports feature to support Security Products. !21892
+- Adds the user's public_email attribute to the API. !21909 (Alexis Reigel)
+- Update all gitlab CI templates from gitlab-org/gitlab-ci-yml. !21929
+- Add support for setting the public email through the api. !21938 (Alexis Reigel)
+- Support db migration and initialization for Auto DevOps. !21955
+- Add subscribe filter to group and project labels pages. !21965
+- Add support for pipeline only/except policy for modified paths. !21981
+- Docs for Project/Groups members API with inherited members. !21984 (Jacopo Beschi @jacopo-beschi)
+- Adds Web IDE commits to usage ping. !22007
+- Add timed incremental rollout to Auto DevOps. !22023
+- Show percentage of language detection on the language bar. !22056 (Johann Hubert Sonntagbauer)
+- Allows to filter issues by Any milestone in the API. !22080 (Jacopo Beschi @jacopo-beschi)
+- Add button to download 2FA codes. (Luke Picciau)
+- Render log artifact files in GitLab.
+
+### Other (42 changes, 16 of them are from the community)
+
+- Send deployment information in job API. !21307
+- Split admin settings into multiple sub pages. !21467
+- Remove Rugged and shell code from Gitlab::Git. !21488
+- Add trigger information in job API. !21495
+- Add empty state illustration information in job API. !21532
+- Add retried jobs to pipeline stage. !21558
+- Rails 5: fix issue move service In rails 5, the attributes method for an enum returns the name instead of the database integer. !21616 (Jasper Maes)
+- Expose project runners in job API. !21618
+- create from template: hide checkbox for initializing repository with readme. !21646
+- Adds new 'Overview' tab on user profile page. !21663
+- Add clean-up phase for ScheduleDiffFilesDeletion migration. !21734
+- Prevents private profile help link from toggling checkbox. !21757
+- Make AutoDevOps work behind proxy. !21775 (Sergej - @kinolaev)
+- Use Vue components and new API to render Artifacts, Trigger Variables and Commit blocks on Job page. !21777
+- Add wrapper rake task to migrate all uploads to OS. !21779
+- Retroactively fill pipeline source for external pipelines. !21814
+- Rename squash before merge vue component. !21851 (George Tsiolis)
+- Fix merge request header margins. !21878
+- Fix committer typo. !21899 (George Tsiolis)
+- Adds an extra width to the responsive tables. !21928
+- Expose has_trace in job API. !21950
+- Rename block scope local variable in table pagination spec. !21969 (George Tsiolis)
+- Fix blue, orange, and red color inconsistencies. !21972
+- Update operations metrics empty state. !21974 (George Tsiolis)
+- Improve empty project placeholder for non-members and members without write access. !21977 (George Tsiolis)
+- Add copy to clipboard button for application id and secret. !21978 (George Tsiolis)
+- Add link component to UserAvatarLink component. !21986 (George Tsiolis)
+- Add link component to DownloadViewer component. !21987 (George Tsiolis)
+- Rephrase 2FA and TOTP documentation and view. !21998 (Marc Schwede)
+- Update project path on project name autofill. !22016
+- Improve logging when username update fails due to registry tags. !22038
+- Align collapsed sidebar avatar container. !22044 (George Tsiolis)
+- Rails5: fix artifacts controller download spec Rails5 has params[:file_type] as '' if file_type is included as nil in the request. !22123 (Jasper Maes)
+- Hide pagination for personal projects on profile overview tab. !22321
+- Extracts scroll position check into reusable functions.
+- Uses Vuex store in job details page and removes old mediator pattern.
+- Render 412 when invalid UTF-8 parameters are passed to controller.
+- Renders Job show page in new Vue app.
+- Add link to User Snippets in breadcrumbs of New User Snippet page. (J.D. Bean)
+- Log project services errors when executing async.
+- Update docs regarding frozen string. (gfyoung)
+- Check frozen string in style builds. (gfyoung)
+
+
## 11.3.6 (2018-10-17)
- No changes.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b33ef79558e..2dc8ac40dd4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -72,7 +72,7 @@ This [documentation](doc/development/contributing/index.md#security-vulnerabilit
## Code of Conduct
-This [documentation](doc/development/contributing/index.md#code-of-conduct) has been moved.
+This [documentation](https://about.gitlab.com/contributing/code-of-conduct/) has been moved.
## Closing policy for issues and merge requests
diff --git a/Dangerfile b/Dangerfile
index 10caacff4c4..469e77b2514 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -1,3 +1,4 @@
+danger.import_plugin('danger/plugins/helper.rb')
danger.import_dangerfile(path: 'danger/metadata')
danger.import_dangerfile(path: 'danger/changes_size')
danger.import_dangerfile(path: 'danger/changelog')
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 33e061fe7a0..bcc9c2840a7 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.125.1
+0.126.0
diff --git a/Gemfile b/Gemfile
index 64d87baf697..c442ed9065e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -417,8 +417,7 @@ end
gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly'
gem 'grpc', '~> 1.15.0'
-# Locked until https://github.com/google/protobuf/issues/4210 is closed
-gem 'google-protobuf', '= 3.5.1'
+gem 'google-protobuf', '~> 3.6'
gem 'toml-rb', '~> 1.0.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index a39788bee9f..bf16bef4f32 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -303,7 +303,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-protobuf (3.5.1)
+ google-protobuf (3.6.1)
googleapis-common-protos-types (1.0.2)
google-protobuf (~> 3.0)
googleauth (0.6.6)
@@ -1005,7 +1005,7 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
google-api-client (~> 0.23)
- google-protobuf (= 3.5.1)
+ google-protobuf (~> 3.6)
gpgme
grape (~> 1.1)
grape-entity (~> 0.7.1)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 1421edb1d39..81547303ed2 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -306,7 +306,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-protobuf (3.5.1)
+ google-protobuf (3.6.1)
googleapis-common-protos-types (1.0.2)
google-protobuf (~> 3.0)
googleauth (0.6.6)
@@ -1014,7 +1014,7 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
google-api-client (~> 0.23)
- google-protobuf (= 3.5.1)
+ google-protobuf (~> 3.6)
gpgme
grape (~> 1.1)
grape-entity (~> 0.7.1)
diff --git a/README.md b/README.md
index 335736e53f5..133c15a83a7 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL/OpenSUSE
-- Ruby (MRI) 2.3
+- Ruby (MRI) 2.4
- Git 2.8.4+
- Redis 2.8+
- PostgreSQL (preferred) or MySQL
diff --git a/VERSION b/VERSION
index 39a87ed80d7..d6ed538af36 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.4.0-pre
+11.5.0-pre
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index 29541e985be..05de970e387 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -6,8 +6,10 @@ import Pager from './pager';
import { localTimeAgo } from './lib/utils/datetime_utility';
export default class Activities {
- constructor() {
- Pager.init(20, true, false, data => data, this.updateTooltips);
+ constructor(container = '') {
+ this.container = container;
+
+ Pager.init(20, true, false, data => data, this.updateTooltips, this.container);
$('.event-filter-link').on('click', e => {
e.preventDefault();
@@ -22,7 +24,7 @@ export default class Activities {
reloadActivities() {
$('.content_list').html('');
- Pager.init(20, true, false, data => data, this.updateTooltips);
+ Pager.init(20, true, false, data => data, this.updateTooltips, this.container);
}
toggleFilter(sender) {
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index 56f64f934a1..720f30e18e6 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -17,7 +17,7 @@ import flash from '~/flash';
export default function renderMermaid($els) {
if (!$els.length) return;
- import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid')
+ import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
mermaid.initialize({
// mermaid core options
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 852d71f4e84..37a3ceb5341 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -40,7 +40,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = (
},
selectable: true,
filterable: true,
- filterRemote: true,
+ filterRemote: !!$dropdown.data('refsUrl'),
fieldName: $dropdown.data('fieldName'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index cfe4273742f..34e836a570a 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -1,17 +1,30 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
+import { TooltipDirective as Tooltip } from '@gitlab-org/gitlab-ui';
+import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowStats from './file_row_stats.vue';
+const treeListStorageKey = 'mr_diff_tree_list';
+
export default {
+ directives: {
+ Tooltip,
+ },
components: {
Icon,
FileRow,
},
data() {
+ const treeListStored = localStorage.getItem(treeListStorageKey);
+ const renderTreeList = treeListStored !== null ?
+ convertPermissionToBoolean(treeListStored) : true;
+
return {
search: '',
+ renderTreeList,
+ focusSearch: false,
};
},
computed: {
@@ -20,15 +33,35 @@ export default {
filteredTreeList() {
const search = this.search.toLowerCase().trim();
- if (search === '') return this.tree;
+ if (search === '') return this.renderTreeList ? this.tree : this.allBlobs;
return this.allBlobs.filter(f => f.name.toLowerCase().indexOf(search) >= 0);
},
+ rowDisplayTextKey() {
+ if (this.renderTreeList && this.search.trim() === '') {
+ return 'name';
+ }
+
+ return 'path';
+ },
},
methods: {
...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']),
clearSearch() {
this.search = '';
+ this.toggleFocusSearch(false);
+ },
+ toggleRenderTreeList(toggle) {
+ this.renderTreeList = toggle;
+ localStorage.setItem(treeListStorageKey, this.renderTreeList);
+ },
+ toggleFocusSearch(toggle) {
+ this.focusSearch = toggle;
+ },
+ blurSearch() {
+ if (this.search.trim() === '') {
+ this.toggleFocusSearch(false);
+ }
},
},
FileRowStats,
@@ -37,28 +70,67 @@ export default {
<template>
<div class="tree-list-holder d-flex flex-column">
- <div class="append-bottom-8 position-relative tree-list-search">
- <icon
- name="search"
- class="position-absolute tree-list-icon"
- />
- <input
- v-model="search"
- :placeholder="s__('MergeRequest|Filter files')"
- type="search"
- class="form-control"
- />
- <button
- v-show="search"
- :aria-label="__('Clear search')"
- type="button"
- class="position-absolute tree-list-icon tree-list-clear-icon border-0 p-0"
- @click="clearSearch"
- >
+ <div class="append-bottom-8 position-relative tree-list-search d-flex">
+ <div class="flex-fill d-flex">
<icon
- name="close"
+ name="search"
+ class="position-absolute tree-list-icon"
+ />
+ <input
+ v-model="search"
+ :placeholder="s__('MergeRequest|Filter files')"
+ type="search"
+ class="form-control"
+ @focus="toggleFocusSearch(true)"
+ @blur="blurSearch"
/>
- </button>
+ <button
+ v-show="search"
+ :aria-label="__('Clear search')"
+ type="button"
+ class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0"
+ @click="clearSearch"
+ >
+ <icon
+ name="close"
+ />
+ </button>
+ </div>
+ <div
+ v-show="!focusSearch"
+ class="btn-group prepend-left-8 tree-list-view-toggle"
+ >
+ <button
+ v-tooltip.hover
+ :aria-label="__('List view')"
+ :title="__('List view')"
+ :class="{
+ active: !renderTreeList
+ }"
+ class="btn btn-default pt-0 pb-0 d-flex align-items-center"
+ type="button"
+ @click="toggleRenderTreeList(false)"
+ >
+ <icon
+ name="hamburger"
+ />
+ </button>
+ <button
+ v-tooltip.hover
+ :aria-label="__('Tree view')"
+ :title="__('Tree view')"
+ :class="{
+ active: renderTreeList
+ }"
+ class="btn btn-default pt-0 pb-0 d-flex align-items-center"
+ type="button"
+ @click="toggleRenderTreeList(true)"
+ >
+ <icon
+ name="file-tree"
+ />
+ </button>
+ </div>
</div>
<div
class="tree-list-scroll"
@@ -72,6 +144,8 @@ export default {
:hide-extra-on-tree="true"
:extra-component="$options.FileRowStats"
:show-changed-icon="true"
+ :display-text-key="rowDisplayTextKey"
+ :should-truncate-start="true"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
/>
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
index c4f0c41d3a8..b70125c80ca 100644
--- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -68,6 +68,11 @@ export const conditions = [
value: 'none',
},
{
+ url: 'milestone_title=Any+Milestone',
+ tokenKey: 'milestone',
+ value: 'any',
+ },
+ {
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
value: 'upcoming',
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index abb30d84fbc..749c09f897c 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -35,12 +35,12 @@ const createAction = config => `
</a>
`;
-const createFlashEl = (message, type, isInContentWrapper = false) => `
+const createFlashEl = (message, type, isFixedLayout = false) => `
<div
class="flash-${type}"
>
<div
- class="flash-text ${isInContentWrapper ? 'container-fluid container-limited' : ''}"
+ class="flash-text ${isFixedLayout ? 'container-fluid container-limited limit-container-width' : ''}"
>
${_.escape(message)}
</div>
@@ -74,12 +74,13 @@ const createFlash = function createFlash(
addBodyClass = false,
) {
const flashContainer = parent.querySelector('.flash-container');
+ const navigation = parent.querySelector('.content');
if (!flashContainer) return null;
- const isInContentWrapper = flashContainer.parentNode.classList.contains('content-wrapper');
+ const isFixedLayout = navigation ? navigation.parentNode.classList.contains('container-limited') : true;
- flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper);
+ flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout);
const flashEl = flashContainer.querySelector(`.flash-${type}`);
removeFlashClickListener(flashEl, fadeTransition);
diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
index e2be805ed22..ec759043efc 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
@@ -25,14 +25,32 @@ export default {
},
},
methods: {
- createFile(target, file, isText) {
+ isText(content, fileType) {
+ const knownBinaryFileTypes = ['image/'];
+ const knownTextFileTypes = ['text/'];
+ const isKnownBinaryFileType = knownBinaryFileTypes.find(type => fileType.includes(type));
+ const isKnownTextFileType = knownTextFileTypes.find(type => fileType.includes(type));
+ const asciiRegex = /^[ -~\t\n\r]+$/; // tests whether a string contains ascii characters only (ranges from space to tilde, tabs and new lines)
+
+ if (isKnownBinaryFileType) {
+ return false;
+ }
+
+ if (isKnownTextFileType) {
+ return true;
+ }
+
+ // if it's not a known file type, determine the type by evaluating the file contents
+ return asciiRegex.test(content);
+ },
+ createFile(target, file) {
const { name } = file;
let { result } = target;
+ const encodedContent = result.split('base64,')[1];
+ const rawContent = encodedContent ? atob(encodedContent) : '';
+ const isText = this.isText(rawContent, file.type);
- if (!isText) {
- // eslint-disable-next-line prefer-destructuring
- result = result.split('base64,')[1];
- }
+ result = isText ? rawContent : encodedContent;
this.$emit('create', {
name: `${this.path ? `${this.path}/` : ''}${name}`,
@@ -43,15 +61,9 @@ export default {
},
readFile(file) {
const reader = new FileReader();
- const isText = file.type.match(/text.*/) !== null;
- reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true });
-
- if (isText) {
- reader.readAsText(file);
- } else {
- reader.readAsDataURL(file);
- }
+ reader.addEventListener('load', e => this.createFile(e.target, file), { once: true });
+ reader.readAsDataURL(file);
},
openFile() {
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index fa35b87ef2b..ba14aaeed2c 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -165,7 +165,7 @@
<gl-loading-icon
v-if="isLoading"
:size="2"
- class="js-job-loading prepend-top-20"
+ class="js-job-loading qa-loading-animation prepend-top-20"
/>
<template v-else-if="shouldRenderContent">
@@ -217,8 +217,8 @@
/>
<!--job log -->
- <div
- v-if="hasTrace"
+ <div
+ v-if="hasTrace"
class="build-trace-container prepend-top-default">
<log-top-bar
:class="{
diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue
new file mode 100644
index 00000000000..6486b25c8a7
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_container_item.vue
@@ -0,0 +1,65 @@
+<script>
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+ components: {
+ CiIcon,
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ isActive: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ tooltipText() {
+ return `${this.job.name} - ${this.job.status.tooltip}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="build-job"
+ :class="{
+ retried: job.retried,
+ active: isActive
+ }"
+ >
+ <a
+ v-tooltip
+ :href="job.status.details_path"
+ :title="tooltipText"
+ data-container="body"
+ data-boundary="viewport"
+ class="js-job-link"
+ >
+ <icon
+ v-if="isActive"
+ name="arrow-right"
+ class="js-arrow-right icon-arrow-right"
+ />
+
+ <ci-icon :status="job.status" />
+
+ <span>{{ job.name ? job.name : job.id }}</span>
+
+ <icon
+ v-if="job.retried"
+ name="retry"
+ class="js-retry-icon"
+ />
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
index accda5d1bd8..ffa6ada3e28 100644
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -42,7 +42,7 @@
};
</script>
<template>
- <pre class="js-build-trace build-trace">
+ <pre class="js-build-trace build-trace qa-build-trace">
<code
class="bash"
v-html="trace"
diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue
index 03f36ec5c8b..951bcb36600 100644
--- a/app/assets/javascripts/jobs/components/jobs_container.vue
+++ b/app/assets/javascripts/jobs/components/jobs_container.vue
@@ -1,17 +1,11 @@
<script>
-import _ from 'underscore';
-import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-import tooltip from '~/vue_shared/directives/tooltip';
+import JobContainerItem from './job_container_item.vue';
export default {
components: {
- CiIcon,
- Icon,
- },
- directives: {
- tooltip,
+ JobContainerItem,
},
+
props: {
jobs: {
type: Array,
@@ -26,49 +20,16 @@ export default {
isJobActive(currentJobId) {
return this.jobId === currentJobId;
},
- tooltipText(job) {
- return `${_.escape(job.name)} - ${job.status.tooltip}`;
- },
},
};
</script>
<template>
<div class="js-jobs-container builds-container">
- <div
+ <job-container-item
v-for="job in jobs"
:key="job.id"
- class="build-job"
- :class="{ retried: job.retried, active: isJobActive(job.id) }"
- >
- <a
- v-tooltip
- :href="job.status.details_path"
- :title="tooltipText(job)"
- data-container="body"
- >
- <icon
- v-if="isJobActive(job.id)"
- name="arrow-right"
- class="js-arrow-right icon-arrow-right"
- />
-
- <ci-icon :status="job.status" />
-
- <span>
- <template v-if="job.name">
- {{ job.name }}
- </template>
- <template v-else>
- {{ job.id }}
- </template>
- </span>
-
- <icon
- v-if="job.retried"
- name="retry"
- class="js-retry-icon"
- />
- </a>
- </div>
+ :job="job"
+ :is-active="isJobActive(job.id)"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 03f3bb42193..2950c2299ab 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -419,7 +419,7 @@ export default class MergeRequestTabs {
if (this.diffViewType() === 'parallel' || removeLimited) {
$wrapper.removeClass('container-limited');
} else {
- $wrapper.addClass('container-limited');
+ $wrapper.toggleClass('container-limited', this.fixedLayoutPref);
}
}
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index 8aabb840847..1c98683c597 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -4,6 +4,7 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import initDiffsApp from '../diffs';
import notesApp from '../notes/components/notes_app.vue';
import discussionCounter from '../notes/components/discussion_counter.vue';
+import initDiscussionFilters from '../notes/discussion_filters';
import store from './stores';
import MergeRequest from '../merge_request';
@@ -88,5 +89,6 @@ export default function initMrNotes() {
},
});
+ initDiscussionFilters(store);
initDiffsApp(store);
}
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index ad6e7cf501d..1f80f24e045 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -56,10 +56,11 @@ export default {
</script>
<template>
- <div class="line-resolve-all-container prepend-top-10">
+ <div
+ v-if="discussionCount > 0"
+ class="line-resolve-all-container prepend-top-8">
<div>
<div
- v-if="discussionCount > 0"
:class="{ 'has-next-btn': hasNextButton }"
class="line-resolve-all">
<span
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
new file mode 100644
index 00000000000..27972682ca1
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -0,0 +1,82 @@
+<script>
+import $ from 'jquery';
+import Icon from '~/vue_shared/components/icon.vue';
+import { mapGetters, mapActions } from 'vuex';
+
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ filters: {
+ type: Array,
+ required: true,
+ },
+ defaultValue: {
+ type: Number,
+ default: null,
+ required: false,
+ },
+ },
+ data() {
+ return { currentValue: this.defaultValue };
+ },
+ computed: {
+ ...mapGetters([
+ 'getNotesDataByProp',
+ ]),
+ currentFilter() {
+ if (!this.currentValue) return this.filters[0];
+ return this.filters.find(filter => filter.value === this.currentValue);
+ },
+ },
+ methods: {
+ ...mapActions(['filterDiscussion']),
+ selectFilter(value) {
+ const filter = parseInt(value, 10);
+
+ // close dropdown
+ $(this.$refs.dropdownToggle).dropdown('toggle');
+
+ if (filter === this.currentValue) return;
+ this.currentValue = filter;
+ this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="discussion-filter-container d-inline-block align-bottom">
+ <button
+ id="discussion-filter-dropdown"
+ ref="dropdownToggle"
+ class="btn btn-default"
+ data-toggle="dropdown"
+ aria-expanded="false"
+ >
+ {{ currentFilter.title }}
+ <icon name="chevron-down" />
+ </button>
+ <div
+ class="dropdown-menu dropdown-menu-selectable dropdown-menu-right"
+ aria-labelledby="discussion-filter-dropdown">
+ <div class="dropdown-content">
+ <ul>
+ <li
+ v-for="filter in filters"
+ :key="filter.value"
+ >
+ <button
+ :class="{ 'is-active': filter.value === currentValue }"
+ type="button"
+ @click="selectFilter(filter.value)"
+ >
+ {{ filter.title }}
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 618a1581d8f..b0faa443a18 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -50,11 +50,11 @@ export default {
},
data() {
return {
- isLoading: true,
+ currentFilter: null,
};
},
computed: {
- ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount']),
+ ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount', 'isLoading']),
noteableType() {
return this.noteableData.noteableType;
},
@@ -102,6 +102,7 @@ export default {
},
methods: {
...mapActions({
+ setLoadingState: 'setLoadingState',
fetchDiscussions: 'fetchDiscussions',
poll: 'poll',
actionToggleAward: 'toggleAward',
@@ -133,19 +134,19 @@ export default {
return discussion.individual_note ? { note: discussion.notes[0] } : { discussion };
},
fetchNotes() {
- return this.fetchDiscussions(this.getNotesDataByProp('discussionsPath'))
+ return this.fetchDiscussions({ path: this.getNotesDataByProp('discussionsPath') })
.then(() => {
this.initPolling();
})
.then(() => {
- this.isLoading = false;
+ this.setLoadingState(false);
this.setNotesFetchedState(true);
eventHub.$emit('fetchedNotesData');
})
.then(() => this.$nextTick())
.then(() => this.checkLocationHash())
.catch(() => {
- this.isLoading = false;
+ this.setLoadingState(false);
this.setNotesFetchedState(true);
Flash('Something went wrong while fetching comments. Please try again.');
});
diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js
new file mode 100644
index 00000000000..012ffc4093e
--- /dev/null
+++ b/app/assets/javascripts/notes/discussion_filters.js
@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import DiscussionFilter from './components/discussion_filter.vue';
+
+export default (store) => {
+ const discussionFilterEl = document.getElementById('js-vue-discussion-filter');
+
+ if (discussionFilterEl) {
+ const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
+ const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
+ const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
+ const filters = Object.keys(filterValues).map(entry =>
+ ({ title: entry, value: filterValues[entry] }));
+
+ return new Vue({
+ el: discussionFilterEl,
+ name: 'DiscussionFilter',
+ components: {
+ DiscussionFilter,
+ },
+ store,
+ render(createElement) {
+ return createElement('discussion-filter', {
+ props: {
+ filters,
+ defaultValue,
+ },
+ });
+ },
+ });
+ }
+
+ return null;
+};
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 3aef30c608c..2f715c85fa6 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,10 +1,13 @@
import Vue from 'vue';
import notesApp from './components/notes_app.vue';
+import initDiscussionFilters from './discussion_filters';
import createStore from './stores';
document.addEventListener('DOMContentLoaded', () => {
const store = createStore();
+ initDiscussionFilters(store);
+
return new Vue({
el: '#js-vue-notes',
components: {
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index f5dce94caad..47a6f07cce2 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -5,8 +5,9 @@ import * as constants from '../constants';
Vue.use(VueResource);
export default {
- fetchDiscussions(endpoint) {
- return Vue.http.get(endpoint);
+ fetchDiscussions(endpoint, filter) {
+ const config = filter !== undefined ? { params: { notes_filter: filter } } : null;
+ return Vue.http.get(endpoint, config);
},
deleteNote(endpoint) {
return Vue.http.delete(endpoint);
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 7ab7e5a9abb..b5dd49bc6c9 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -11,6 +11,7 @@ import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
+import { __ } from '~/locale';
let eTagPoll;
@@ -36,9 +37,9 @@ export const setNotesFetchedState = ({ commit }, state) =>
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
-export const fetchDiscussions = ({ commit }, path) =>
+export const fetchDiscussions = ({ commit }, { path, filter }) =>
service
- .fetchDiscussions(path)
+ .fetchDiscussions(path, filter)
.then(res => res.json())
.then(discussions => {
commit(types.SET_INITIAL_DISCUSSIONS, discussions);
@@ -251,7 +252,7 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
} else if (note.type === constants.DIFF_NOTE) {
- dispatch('fetchDiscussions', state.notesData.discussionsPath);
+ dispatch('fetchDiscussions', { path: state.notesData.discussionsPath });
} else {
commit(types.ADD_NEW_NOTE, note);
}
@@ -345,5 +346,23 @@ export const updateMergeRequestWidget = () => {
mrWidgetEventHub.$emit('mr.discussion.updated');
};
+export const setLoadingState = ({ commit }, data) => {
+ commit(types.SET_NOTES_LOADING_STATE, data);
+};
+
+export const filterDiscussion = ({ dispatch }, { path, filter }) => {
+ dispatch('setLoadingState', true);
+ dispatch('fetchDiscussions', { path, filter })
+ .then(() => {
+ dispatch('setLoadingState', false);
+ dispatch('setNotesFetchedState', true);
+ })
+ .catch(() => {
+ dispatch('setLoadingState', false);
+ dispatch('setNotesFetchedState', true);
+ Flash(__('Something went wrong while fetching comments. Please try again.'));
+ });
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index a829149a17e..21c334a9d33 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -11,6 +11,8 @@ export const getNotesData = state => state.notesData;
export const isNotesFetched = state => state.isNotesFetched;
+export const isLoading = state => state.isLoading;
+
export const getNotesDataByProp = state => prop => state.notesData[prop];
export const getNoteableData = state => state.noteableData;
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 61dbb075586..400142668ea 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -11,6 +11,7 @@ export default () => ({
// View layer
isToggleStateButtonLoading: false,
isNotesFetched: false,
+ isLoading: true,
// holds endpoints and permissions provided through haml
notesData: {
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 6f374f78691..2fa53aef1d4 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -14,6 +14,7 @@ export const UPDATE_NOTE = 'UPDATE_NOTE';
export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
+export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
// DISCUSSION
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 73e55705f39..65085452139 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -216,6 +216,10 @@ export default {
Object.assign(state, { isNotesFetched: value });
},
+ [types.SET_NOTES_LOADING_STATE](state, value) {
+ state.isLoading = value;
+ },
+
[types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index 3b58c54b3f4..386a9b2c740 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -7,14 +7,21 @@ const ENDLESS_SCROLL_BOTTOM_PX = 400;
const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000;
export default {
- init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
+ init(
+ limit = 0,
+ preload = false,
+ disable = false,
+ prepareData = $.noop,
+ callback = $.noop,
+ container = '',
+ ) {
this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
this.limit = limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable;
this.prepareData = prepareData;
this.callback = callback;
- this.loading = $('.loading').first();
+ this.loading = $(`${container} .loading`).first();
if (preload) {
this.offset = 0;
this.getOld();
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index d0bce857029..32b55575f95 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -5,6 +5,7 @@ import initSettingsPanels from '~/settings_panels';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import { GROUP_BADGE } from '~/badges/constants';
+import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
groupAvatar();
@@ -15,4 +16,6 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'),
);
mountBadgeSettings(GROUP_BADGE);
+
+ projectSelect();
});
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 1de9945baad..04bcb16f036 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -170,7 +170,7 @@ export default class UserTabs {
this.loadActivityCalendar('activity');
// eslint-disable-next-line no-new
- new Activities();
+ new Activities('#activity');
this.loaded.activity = true;
}
diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
index fb8c6402d02..b373d83a44b 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -82,7 +82,7 @@
:loading-text="groupedSummaryText"
:error-text="groupedSummaryText"
:has-issues="reports.length > 0"
- class="mr-widget-border-top grouped-security-reports mr-report"
+ class="mr-widget-section grouped-security-reports mr-report"
>
<div
slot="body"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 9161f703697..6c87287a4c4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -1,6 +1,7 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import tooltip from '../../vue_shared/directives/tooltip';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
@@ -18,6 +19,7 @@ export default {
StatusIcon,
Icon,
TooltipOnTruncate,
+ FilteredSearchDropdown,
},
directives: {
tooltip,
@@ -30,8 +32,10 @@ export default {
},
},
data() {
+ const features = window.gon.features || {};
return {
isStopping: false,
+ enableCiEnvironmentsStatusChanges: features.ciEnvironmentsStatusChanges,
};
},
computed: {
@@ -118,18 +122,65 @@ export default {
/>
</div>
<div>
- <a
- v-if="hasExternalUrls"
- :href="deployment.external_url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="deploy-link js-deploy-url btn btn-default btn-sm inline"
- >
- <span>
- View app
- <icon name="external-link" />
- </span>
- </a>
+ <template v-if="hasExternalUrls">
+ <filtered-search-dropdown
+ v-if="enableCiEnvironmentsStatusChanges"
+ class="js-mr-wigdet-deployment-dropdown inline"
+ :items="deployment.changes"
+ :main-action-link="deployment.external_url"
+ filter-key="path"
+ >
+ <template
+ slot="mainAction"
+ slot-scope="slotProps"
+ >
+ <a
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="deploy-link js-deploy-url inline"
+ :class="slotProps.className"
+ >
+ <span>
+ {{ __('View app') }}
+ <icon name="external-link" />
+ </span>
+ </a>
+ </template>
+
+ <template
+ slot="result"
+ slot-scope="slotProps"
+ >
+ <a
+ :href="slotProps.result.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="menu-item"
+ >
+ <strong class="str-truncated-100 append-bottom-0 d-block">
+ {{ slotProps.result.path }}
+ </strong>
+
+ <p class="text-secondary str-truncated-100 append-bottom-0 d-block">
+ {{ slotProps.result.external_url }}
+ </p>
+ </a>
+ </template>
+ </filtered-search-dropdown>
+ <a
+ v-else
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline"
+ >
+ <span>
+ {{ __('View app') }}
+ <icon name="external-link" />
+ </span>
+ </a>
+ </template>
<loading-button
v-if="deployment.stop_url"
:loading="isStopping"
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 7ac3fedb2e3..8180f13a7cb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -112,7 +112,8 @@ export default {
eventHub.$on('mr.discussion.updated', this.checkStatus);
},
mounted() {
- this.handleMounted();
+ this.setFaviconHelper();
+ this.initDeploymentsPolling();
},
beforeDestroy() {
eventHub.$off('mr.discussion.updated', this.checkStatus);
@@ -250,10 +251,6 @@ export default {
this.stopPolling();
});
},
- handleMounted() {
- this.setFaviconHelper();
- this.initDeploymentsPolling();
- },
},
};
</script>
@@ -275,12 +272,13 @@ export default {
:key="deployment.id"
:deployment="deployment"
/>
- <grouped-test-reports-app
- v-if="mr.testResultsPath"
- class="js-reports-container"
- :endpoint="mr.testResultsPath"
- />
<div class="mr-section-container">
+ <grouped-test-reports-app
+ v-if="mr.testResultsPath"
+ class="js-reports-container"
+ :endpoint="mr.testResultsPath"
+ />
+
<div class="mr-widget-section">
<component
:is="componentName"
diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
index a2518e2a611..c60052fec50 100644
--- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
@@ -43,7 +43,7 @@ export default {
computed: {
cssClass() {
const className = this.status.group;
- return className ? `ci-status ci-${className}` : 'ci-status';
+ return className ? `ci-status ci-${className} qa-status-badge` : 'ci-status qa-status-badge';
},
},
};
diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
index 19611b14be4..bffaa096210 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
+++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
@@ -549,6 +549,7 @@ const fileNameIcons = {
jenkinsfile: 'jenkins',
'firebase.json': 'firebase',
'.firebaserc': 'firebase',
+ Rakefile: 'ruby',
'rollup.config.js': 'rollup',
'rollup.config.ts': 'rollup',
'rollup-config.js': 'rollup',
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 36a345130c0..2d89a156117 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -34,10 +34,21 @@ export default {
required: false,
default: false,
},
+ displayTextKey: {
+ type: String,
+ required: false,
+ default: 'name',
+ },
+ shouldTruncateStart: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
mouseOver: false,
+ truncateStart: 0,
};
},
computed: {
@@ -60,6 +71,15 @@ export default {
'is-open': this.file.opened,
};
},
+ outputText() {
+ const text = this.file[this.displayTextKey];
+
+ if (this.truncateStart === 0) {
+ return text;
+ }
+
+ return `...${text.substring(this.truncateStart, text.length)}`;
+ },
},
watch: {
'file.active': function fileActiveWatch(active) {
@@ -72,6 +92,15 @@ export default {
if (this.hasPathAtCurrentRoute()) {
this.scrollIntoView(true);
}
+
+ if (this.shouldTruncateStart) {
+ const { scrollWidth, offsetWidth } = this.$refs.textOutput;
+ const textOverflow = scrollWidth - offsetWidth;
+
+ if (textOverflow > 0) {
+ this.truncateStart = Math.ceil(textOverflow / 5) + 3;
+ }
+ }
},
methods: {
toggleTreeOpen(path) {
@@ -139,6 +168,7 @@ export default {
class="file-row-name-container"
>
<span
+ ref="textOutput"
:style="levelIndentation"
class="file-row-name str-truncated"
>
@@ -156,7 +186,7 @@ export default {
:size="16"
class="append-right-5"
/>
- {{ file.name }}
+ {{ outputText }}
</span>
<component
:is="extraComponent"
@@ -175,6 +205,8 @@ export default {
:hide-extra-on-tree="hideExtraOnTree"
:extra-component="extraComponent"
:show-changed-icon="showChangedIcon"
+ :display-text-key="displayTextKey"
+ :should-truncate-start="shouldTruncateStart"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="clickedFile"
/>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
new file mode 100644
index 00000000000..460fa6ad72e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
@@ -0,0 +1,143 @@
+<script>
+import $ from 'jquery';
+import Icon from '~/vue_shared/components/icon.vue';
+/**
+ * Renders a split dropdown with
+ * an input that allows to search through the given
+ * array of options.
+ */
+export default {
+ name: 'FilteredSearchDropdown',
+ components: {
+ Icon,
+ },
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ buttonType: {
+ required: false,
+ validator: value =>
+ ['primary', 'default', 'secondary', 'success', 'info', 'warning', 'danger'].indexOf(
+ value,
+ ) !== -1,
+ default: 'default',
+ },
+ size: {
+ required: false,
+ type: String,
+ default: 'sm',
+ },
+ items: {
+ type: Array,
+ required: true,
+ },
+ visibleItems: {
+ type: Number,
+ required: false,
+ default: 5,
+ },
+ filterKey: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ filter: '',
+ };
+ },
+ computed: {
+ className() {
+ return `btn btn-${this.buttonType} btn-${this.size}`;
+ },
+ filteredResults() {
+ if (this.filter !== '') {
+ return this.items.filter(
+ item => item[this.filterKey] && item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()),
+ );
+ }
+
+ return this.items.slice(0, this.visibleItems);
+ }
+ },
+ mounted() {
+ /**
+ * Resets the filter every time the user closes the dropdown
+ */
+ $(this.$el)
+ .on('shown.bs.dropdown', () => {
+ this.$nextTick(() => this.$refs.searchInput.focus());
+ })
+ .on('hidden.bs.dropdown', () => {
+ this.filter = '';
+ });
+ },
+};
+</script>
+<template>
+ <div class="dropdown">
+ <div class="btn-group">
+ <slot
+ name="mainAction"
+ :class-name="className"
+ >
+ <button
+ type="button"
+ :class="className"
+ >
+ {{ title }}
+ </button>
+ </slot>
+
+ <button
+ type="button"
+ :class="className"
+ class="dropdown-toggle dropdown-toggle-split"
+ data-toggle="dropdown"
+ aria-haspopup="true"
+ aria-expanded="false"
+ aria-label="Expand dropdown"
+ >
+ <icon
+ name="angle-down"
+ :size="12"
+ />
+ </button>
+ <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-input">
+ <input
+ ref="searchInput"
+ v-model="filter"
+ type="search"
+ placeholder="Filter"
+ class="js-filtered-dropdown-input dropdown-input-field"
+ />
+ <icon
+ class="dropdown-input-search"
+ name="search"
+ />
+ </div>
+
+ <div class="dropdown-content">
+ <ul>
+ <li
+ v-for="(result, i) in filteredResults"
+ :key="i"
+ class="js-filtered-dropdown-result"
+ >
+ <slot
+ name="result"
+ :result="result"
+ >
+ {{ result[filterKey] }}
+ </slot>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 704adf7864f..3ddb39730c4 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,16 +1,16 @@
<script>
import $ from 'jquery';
-import tooltip from '../../directives/tooltip';
-import toolbarButton from './toolbar_button.vue';
-import icon from '../icon.vue';
+import Tooltip from '../../directives/tooltip';
+import ToolbarButton from './toolbar_button.vue';
+import Icon from '../icon.vue';
export default {
directives: {
- tooltip,
+ Tooltip,
},
components: {
- toolbarButton,
- icon,
+ ToolbarButton,
+ Icon,
},
props: {
previewMarkdown: {
@@ -68,27 +68,27 @@ export default {
:class="{ active: !previewMarkdown }"
class="md-header-tab"
>
- <a
+ <button
class="js-write-link"
- href="#md-write-holder"
tabindex="-1"
- @click.prevent="writeMarkdownTab($event)"
+ type="button"
+ @click="writeMarkdownTab($event)"
>
Write
- </a>
+ </button>
</li>
<li
:class="{ active: previewMarkdown }"
class="md-header-tab"
>
- <a
+ <button
class="js-preview-link js-md-preview-button"
- href="#md-preview-holder"
tabindex="-1"
- @click.prevent="previewMarkdownTab($event)"
+ type="button"
+ @click="previewMarkdownTab($event)"
>
Preview
- </a>
+ </button>
</li>
<li
:class="{ active: !previewMarkdown }"
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index af73954bd2e..1e00aa4ff7e 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -238,10 +238,6 @@ h3.popover-header {
}
.card {
- .card-title {
- margin-bottom: 0;
- }
-
&.card-without-border {
@extend .border-0;
}
@@ -255,13 +251,6 @@ h3.popover-header {
}
}
-.card-header {
- h3.card-title,
- h4.card-title {
- margin-top: 0;
- }
-}
-
.nav-tabs {
// Override bootstrap's default border
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 4ffb3e9ab42..4041f2b4479 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -51,6 +51,7 @@
@import 'framework/blank';
@import 'framework/wells';
@import 'framework/page_header';
+@import 'framework/page_title';
@import 'framework/awards';
@import 'framework/images';
@import 'framework/broadcast_messages';
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index 50ebc6d0dd1..b8bb9e1e07b 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -161,6 +161,7 @@
.nav-links li {
&.active a,
+ &.md-header-tab.active button,
a.active {
border-bottom: 2px solid $active-tab-border;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index c430009bfe0..d1ce3a582bb 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -530,9 +530,6 @@
.header-user {
&.show .dropdown-menu {
- width: auto;
- min-width: unset;
- max-height: 323px;
margin-top: 4px;
color: $gl-text-color;
left: auto;
@@ -544,15 +541,19 @@
display: block;
}
- .user-status-emoji {
+ .user-status {
margin-right: 0;
- display: block;
- vertical-align: text-top;
- max-width: 148px;
- font-size: 12px;
+ max-width: 240px;
+ font-size: $gl-font-size-small;
gl-emoji {
- font-size: $gl-font-size;
+ font-size: $gl-font-size-small;
+ }
+
+ .user-status-emoji {
+ gl-emoji {
+ font-size: $gl-font-size;
+ }
}
}
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 554e2b6720a..3142f94b192 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -72,6 +72,7 @@
.md-header-tab {
@include media-breakpoint-down(xs) {
flex: 1;
+ flex-direction: column;
width: 100%;
border-bottom: 1px solid $border-color;
text-align: center;
diff --git a/app/assets/stylesheets/framework/page_title.scss b/app/assets/stylesheets/framework/page_title.scss
new file mode 100644
index 00000000000..e8302953a63
--- /dev/null
+++ b/app/assets/stylesheets/framework/page_title.scss
@@ -0,0 +1,18 @@
+.page-title-holder {
+ @extend .d-flex;
+ @extend .align-items-center;
+
+ padding-top: $gl-padding-top;
+ border-bottom: 1px solid $border-color;
+
+ .page-title {
+ margin: $gl-padding 0;
+ font-size: 1.75em;
+ font-weight: $gl-font-weight-bold;
+ color: $gl-text-color;
+ }
+
+ .page-title-controls {
+ margin-left: auto;
+ }
+}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 5ca4d944d73..3a117106cff 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -53,8 +53,3 @@
margin-top: $gl-padding;
}
}
-
-.card-title {
- font-size: inherit;
- line-height: inherit;
-}
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 8bab8cf36b1..f47dfe1b563 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -8,15 +8,17 @@
height: auto;
border-bottom: 1px solid $border-color;
- li {
+ li:not(.md-header-toolbar) {
display: flex;
- a {
+ a,
+ button {
padding: $gl-btn-padding;
padding-bottom: 11px;
font-size: 14px;
line-height: 28px;
color: $gl-text-color-secondary;
+ border: 0;
border-bottom: 2px solid transparent;
white-space: nowrap;
@@ -33,7 +35,13 @@
}
}
+ button {
+ padding-top: 0;
+ background-color: transparent;
+ }
+
&.active a,
+ &.active button,
a.active {
color: $black;
font-weight: $gl-font-weight-bold;
@@ -42,6 +50,10 @@
color: $black;
}
}
+
+ &.md-header-tab button {
+ line-height: 19px;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss
index 7cda674e5c8..3f4be8829d7 100644
--- a/app/assets/stylesheets/framework/terms.scss
+++ b/app/assets/stylesheets/framework/terms.scss
@@ -19,17 +19,12 @@
justify-content: space-between;
line-height: $line-height-base;
- .card-title {
+ .logo-text {
+ width: 55px;
+ height: 24px;
display: flex;
- align-items: center;
-
- .logo-text {
- width: 55px;
- height: 24px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
+ flex-direction: column;
+ justify-content: center;
}
.navbar-collapse {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 0fde6e18cc7..ad66a0365ed 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -194,6 +194,7 @@ $well-light-text-color: #5b6169;
* Text
*/
$gl-font-size: 14px;
+$gl-font-size-small: 12px;
$gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
$gl-text-color: #2e2e2e;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 8d884ad6891..52c91266ff4 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1027,8 +1027,12 @@
overflow-x: auto;
}
-.tree-list-search .form-control {
- padding-left: 30px;
+.tree-list-search {
+ flex: 0 0 34px;
+
+ .form-control {
+ padding-left: 30px;
+ }
}
.tree-list-icon {
@@ -1063,3 +1067,9 @@
}
}
}
+
+.tree-list-view-toggle {
+ svg {
+ top: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 0f95fb911e1..8ea34f5d19d 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -185,7 +185,17 @@ ul.related-merge-requests > li {
}
.new-branch-col {
- padding-top: 10px;
+ font-size: 0;
+
+ .discussion-filter-container {
+ &:not(:only-child) {
+ margin-right: $gl-padding-8;
+ }
+
+ @include media-breakpoint-down(md) {
+ margin-top: $gl-padding-8;
+ }
+ }
}
.create-mr-dropdown-wrap {
@@ -205,6 +215,10 @@ ul.related-merge-requests > li {
.btn-group:not(.hidden) {
display: flex;
+
+ @include media-breakpoint-down(md) {
+ margin-top: $gl-padding-8;
+ }
}
.js-create-merge-request {
@@ -251,7 +265,6 @@ ul.related-merge-requests > li {
.new-branch-col {
padding-top: 0;
- text-align: right;
align-self: center;
}
@@ -262,3 +275,9 @@ ul.related-merge-requests > li {
}
}
}
+
+@include media-breakpoint-up(lg) {
+ .new-branch-col {
+ text-align: right;
+ }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 895db89f289..fa6afbf81de 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -47,7 +47,6 @@
}
}
-
.mr-widget-heading {
position: relative;
border: 1px solid $border-color;
@@ -454,7 +453,7 @@
.mr-list {
.merge-request {
- padding: 10px 0 10px 15px;
+ padding: 10px 0 10px 15px;
position: relative;
display: -webkit-flex;
display: flex;
@@ -468,7 +467,6 @@
margin-bottom: 2px;
.ci-status-link {
-
svg {
height: 16px;
width: 16px;
@@ -698,7 +696,6 @@
.table-holder {
.ci-table {
-
th {
background-color: $white-light;
color: $gl-text-color-secondary;
@@ -775,7 +772,7 @@
&.affix {
left: 0;
- transition: right .15s;
+ transition: right 0.15s;
@include media-breakpoint-down(xs) {
right: 0;
@@ -821,9 +818,17 @@
display: flex;
justify-content: space-between;
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(md) {
flex-direction: column-reverse;
}
+
+ .discussion-filter-container {
+ margin-top: $gl-padding-8;
+
+ &:not(:only-child) {
+ padding-right: $gl-padding-8;
+ }
+ }
}
.limit-container-width:not(.container-limited) {
@@ -884,7 +889,7 @@
}
> *:not(:last-child) {
- margin-right: .3em;
+ margin-right: 0.3em;
}
svg {
@@ -907,6 +912,10 @@
.btn svg {
fill: $theme-gray-700;
}
+
+ .dropdown-menu {
+ width: 400px;
+ }
}
// Hack alert: we've rewritten `btn` class in a way that
@@ -917,7 +926,7 @@
&[disabled] {
cursor: not-allowed;
box-shadow: none;
- opacity: .65;
+ opacity: 0.65;
&:hover {
color: $gl-gray-500;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index bfba1bf1b2b..be535ade0a6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -618,7 +618,6 @@ ul.notes {
.line-resolve-all-container {
@include notes-media('min', map-get($grid-breakpoints, sm)) {
margin-right: 0;
- padding-left: $gl-padding;
}
> div {
@@ -756,3 +755,23 @@ ul.notes {
margin-top: 4px;
}
}
+
+.discussion-filter-container {
+
+ .btn > svg {
+ width: $gl-col-padding;
+ height: $gl-col-padding;
+ }
+
+ .dropdown-menu {
+ margin-bottom: $gl-padding-4;
+
+ @include media-breakpoint-down(md) {
+ margin-left: $btn-side-margin + $contextual-sidebar-collapsed-width;
+ }
+
+ @include media-breakpoint-down(xs) {
+ margin-left: $btn-side-margin;
+ }
+ }
+}
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 00d2cc01192..6fc336714b6 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -6,11 +6,9 @@ class Admin::ApplicationsController < Admin::ApplicationController
before_action :set_application, only: [:show, :edit, :update, :destroy]
before_action :load_scopes, only: [:new, :create, :edit, :update]
- # rubocop: disable CodeReuse/ActiveRecord
def index
- @applications = Doorkeeper::Application.where("owner_id IS NULL")
+ @applications = ApplicationsFinder.new.execute
end
- # rubocop: enable CodeReuse/ActiveRecord
def show
end
@@ -49,11 +47,9 @@ class Admin::ApplicationsController < Admin::ApplicationController
private
- # rubocop: disable CodeReuse/ActiveRecord
def set_application
- @application = Doorkeeper::Application.where("owner_id IS NULL").find(params[:id])
+ @application = ApplicationsFinder.new(id: params[:id]).execute
end
- # rubocop: enable CodeReuse/ActiveRecord
# Only allow a trusted parameter "white list" through.
def application_params
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index b5fb5511638..23cc9ee247a 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -3,8 +3,8 @@
class Admin::DashboardController < Admin::ApplicationController
include CountHelper
- COUNTED_ITEMS = [Project, User, Group, ForkedProjectLink, Issue, MergeRequest,
- Note, Snippet, Key, Milestone].freeze
+ COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue,
+ MergeRequest, Note, Snippet, Key, Milestone].freeze
# rubocop: disable CodeReuse/ActiveRecord
def index
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 3766b64a091..0d5c8657c9e 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -20,7 +20,7 @@ class AutocompleteController < ApplicationController
end
def user
- user = UserFinder.new(params).execute!
+ user = UserFinder.new(params[:id]).find_by_id!
render json: UserSerializer.new.represent(user)
end
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 4f3d737e3ce..7f874687212 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -18,10 +18,15 @@ module Boards
list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
issues = list_service.execute
issues = issues.page(params[:page]).per(params[:per] || 20).without_count
- make_sure_position_is_set(issues) if Gitlab::Database.read_write?
- issues = issues.preload(:project,
- :milestone,
+ Issue.move_to_end(issues) if Gitlab::Database.read_write?
+ issues = issues.preload(:milestone,
:assignees,
+ project: [
+ :route,
+ {
+ namespace: [:route]
+ }
+ ],
labels: [:priorities],
notes: [:award_emoji, :author]
)
@@ -60,12 +65,6 @@ module Boards
render json: data
end
- def make_sure_position_is_set(issues)
- issues.each do |issue|
- issue.move_to_end && issue.save unless issue.relative_position
- end
- end
-
def issue
@issue ||= issues_finder.find(params[:id])
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 07e01e903ea..ad9cc0925b7 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -2,6 +2,7 @@
module IssuableActions
extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
included do
before_action :labels, only: [:show, :new, :edit]
@@ -95,10 +96,14 @@ module IssuableActions
def discussions
notes = issuable.discussion_notes
.inc_relations_for_view
+ .with_notes_filter(notes_filter)
.includes(:noteable)
.fresh
- notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes)
+ if notes_filter != UserPreference::NOTES_FILTERS[:only_comments]
+ notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes)
+ end
+
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
@@ -110,6 +115,32 @@ module IssuableActions
private
+ def notes_filter
+ strong_memoize(:notes_filter) do
+ notes_filter_param = params[:notes_filter]&.to_i
+
+ # GitLab Geo does not expect database UPDATE or INSERT statements to happen
+ # on GET requests.
+ # This is just a fail-safe in case notes_filter is sent via GET request in GitLab Geo.
+ if Gitlab::Database.read_only?
+ notes_filter_param || current_user&.notes_filter_for(issuable)
+ else
+ notes_filter = current_user&.set_notes_filter(notes_filter_param, issuable) || notes_filter_param
+
+ # We need to invalidate the cache for polling notes otherwise it will
+ # ignore the filter.
+ # The ideal would be to invalidate the cache for each user.
+ issuable.expire_note_etag_cache if notes_filter_updated?
+
+ notes_filter
+ end
+ end
+ end
+
+ def notes_filter_updated?
+ current_user&.user_preference&.previous_changes&.any?
+ end
+
def discussion_serializer
DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user, note_entity: ProjectNoteEntity)
end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 3a45d6205ab..777b147e2dd 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -17,10 +17,17 @@ module NotesActions
notes_json = { notes: [], last_fetched_at: current_fetched_at }
- notes = notes_finder.execute
- .inc_relations_for_view
+ notes = notes_finder
+ .execute
+ .inc_relations_for_view
+
+ if notes_filter != UserPreference::NOTES_FILTERS[:only_comments]
+ notes =
+ ResourceEvents::MergeIntoNotesService
+ .new(noteable, current_user, last_fetched_at: current_fetched_at)
+ .execute(notes)
+ end
- notes = ResourceEvents::MergeIntoNotesService.new(noteable, current_user, last_fetched_at: current_fetched_at).execute(notes)
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
@@ -224,6 +231,10 @@ module NotesActions
request.headers['X-Last-Fetched-At']
end
+ def notes_filter
+ current_user&.notes_filter_for(params[:target_type])
+ end
+
def notes_finder
@notes_finder ||= NotesFinder.new(project, current_user, finder_params)
end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 1dfa814cdd5..e3eec5a020d 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -20,7 +20,7 @@ class Import::GithubController < Import::BaseController
end
def personal_access_token
- session[access_token_key] = params[:personal_access_token]
+ session[access_token_key] = params[:personal_access_token]&.strip
redirect_to status_import_url
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 01801c31327..3c3dc03a4ee 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -38,7 +38,7 @@ class Profiles::KeysController < Profiles::ApplicationController
def get_keys
if params[:username].present?
begin
- user = User.find_by_username(params[:username])
+ user = UserFinder.new(params[:username]).find_by_username
if user.present?
render text: user.all_ssh_keys.join("\n"), content_type: "text/plain"
else
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index fb2cfdedd9b..56a884b8a2a 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -9,7 +9,7 @@ class Projects::BlobController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
prepend_before_action :authenticate_user!, only: [:edit]
- before_action :set_request_format, only: [:edit, :show, :update]
+ before_action :set_request_format, only: [:edit, :show, :update, :destroy]
before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code!
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 8bc3a81d771..757b03d0b0e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -14,6 +14,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
+ before_action do
+ push_frontend_feature_flag(:ci_environments_status_changes)
+ end
def index
@merge_requests = @issuables
@@ -198,43 +201,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def ci_environments_status
- environments =
- begin
- @merge_request.environments_for(current_user).map do |environment|
- project = environment.project
- deployment = environment.first_deployment_for(@merge_request.diff_head_sha)
-
- stop_url =
- if can?(current_user, :stop_environment, environment)
- stop_project_environment_path(project, environment)
- end
-
- metrics_url =
- if can?(current_user, :read_environment, environment) && environment.has_metrics?
- metrics_project_environment_deployment_path(project, environment, deployment)
- end
-
- metrics_monitoring_url =
- if can?(current_user, :read_environment, environment)
- environment_metrics_path(environment)
- end
-
- {
- id: environment.id,
- name: environment.name,
- url: project_environment_path(project, environment),
- metrics_url: metrics_url,
- metrics_monitoring_url: metrics_monitoring_url,
- stop_url: stop_url,
- external_url: environment.external_url,
- external_url_formatted: environment.formatted_external_url,
- deployed_at: deployment.try(:created_at),
- deployed_at_formatted: deployment.try(:formatted_deployment_time)
- }
- end.compact
- end
+ environments = @merge_request.environments_for(current_user).map do |environment|
+ EnvironmentStatus.new(environment, @merge_request)
+ end
- render json: environments
+ render json: EnvironmentStatusSerializer.new(current_user: current_user).represent(environments)
end
def rebase
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 4bac763d000..3152a38fd8e 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
alias_method :awardable, :note
def finder_params
- params.merge(last_fetched_at: last_fetched_at)
+ params.merge(last_fetched_at: last_fetched_at, notes_filter: notes_filter)
end
def authorize_admin_note!
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 694c3a59e2b..dd9bf17cf0c 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -26,12 +26,9 @@ class SnippetsController < ApplicationController
layout 'snippets'
respond_to :html
- # rubocop: disable CodeReuse/ActiveRecord
def index
if params[:username].present?
- @user = User.find_by(username: params[:username])
-
- return render_404 unless @user
+ @user = UserFinder.new(params[:username]).find_by_username!
@snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope])
.execute.page(params[:page])
@@ -41,7 +38,6 @@ class SnippetsController < ApplicationController
redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def new
@snippet = PersonalSnippet.new
diff --git a/app/finders/applications_finder.rb b/app/finders/applications_finder.rb
new file mode 100644
index 00000000000..3ded90f3fd5
--- /dev/null
+++ b/app/finders/applications_finder.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class ApplicationsFinder
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
+ def execute
+ applications = Doorkeeper::Application.where(owner_id: nil) # rubocop: disable CodeReuse/ActiveRecord
+ by_id(applications)
+ end
+
+ private
+
+ def by_id(applications)
+ return applications unless params[:id]
+
+ Doorkeeper::Application.find_by(id: params[:id]) # rubocop: disable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 1f98ecf95ca..8abfe0c4c17 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -256,7 +256,7 @@ class IssuableFinder
if assignee_id?
User.find_by(id: params[:assignee_id])
elsif assignee_username?
- User.find_by(username: params[:assignee_username])
+ User.find_by_username(params[:assignee_username])
else
nil
end
@@ -284,7 +284,7 @@ class IssuableFinder
if author_id?
User.find_by(id: params[:author_id])
elsif author_username?
- User.find_by(username: params[:author_username])
+ User.find_by_username(params[:author_username])
else
nil
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index c67c2065440..817aac8b5d5 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -24,6 +24,8 @@ class NotesFinder
def execute
notes = init_collection
notes = since_fetch_at(notes)
+ notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter?
+
notes.fresh
end
@@ -134,4 +136,8 @@ class NotesFinder
last_fetched_at = Time.at(@params.fetch(:last_fetched_at, 0).to_i)
notes.updated_after(last_fetched_at - FETCH_OVERLAP)
end
+
+ def notes_filter?
+ @params[:notes_filter].present?
+ end
end
diff --git a/app/finders/user_finder.rb b/app/finders/user_finder.rb
index 815388c894e..556be4c4338 100644
--- a/app/finders/user_finder.rb
+++ b/app/finders/user_finder.rb
@@ -7,22 +7,52 @@
# times we may want to exclude blocked user. By using this finder (and extending
# it whenever necessary) we can keep this logic in one place.
class UserFinder
- attr_reader :params
+ def initialize(username_or_id)
+ @username_or_id = username_or_id
+ end
+
+ # Tries to find a User by id, returning nil if none could be found.
+ def find_by_id
+ User.find_by_id(@username_or_id)
+ end
- def initialize(params)
- @params = params
+ # Tries to find a User by id, raising a `ActiveRecord::RecordNotFound` if it could
+ # not be found.
+ def find_by_id!
+ User.find(@username_or_id)
end
- # Tries to find a User, returning nil if none could be found.
- # rubocop: disable CodeReuse/ActiveRecord
- def execute
- User.find_by(id: params[:id])
+ # Tries to find a User by username, returning nil if none could be found.
+ def find_by_username
+ User.find_by_username(@username_or_id)
end
- # rubocop: enable CodeReuse/ActiveRecord
- # Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could
+ # Tries to find a User by username, raising a `ActiveRecord::RecordNotFound` if it could
# not be found.
- def execute!
- User.find(params[:id])
+ def find_by_username!
+ User.find_by_username!(@username_or_id)
+ end
+
+ # Tries to find a User by username or id, returning nil if none could be found.
+ def find_by_id_or_username
+ if input_is_id?
+ find_by_id
+ else
+ find_by_username
+ end
+ end
+
+ # Tries to find a User by username or id, raising a `ActiveRecord::RecordNotFound` if it could
+ # not be found.
+ def find_by_id_or_username!
+ if input_is_id?
+ find_by_id!
+ else
+ find_by_username!
+ end
+ end
+
+ def input_is_id?
+ @username_or_id.is_a?(Numeric) || @username_or_id =~ /^\d+$/
end
end
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index f2ad9b4bda5..81ae50c0bd1 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -43,13 +43,11 @@ class UsersFinder
private
- # rubocop: disable CodeReuse/ActiveRecord
def by_username(users)
return users unless params[:username]
- users.where(username: params[:username])
+ users.by_username(params[:username])
end
- # rubocop: enable CodeReuse/ActiveRecord
def by_search(users)
return users unless params[:search].present?
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index f7e087a6234..ff7f1e3a9aa 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -183,23 +183,23 @@ module BlobHelper
end
private :template_dropdown_names
- def licenses_for_select(project = @project)
+ def licenses_for_select(project)
@licenses_for_select ||= template_dropdown_names(TemplateFinder.build(:licenses, project).execute)
end
- def gitignore_names(project = @project)
+ def gitignore_names(project)
@gitignore_names ||= template_dropdown_names(TemplateFinder.build(:gitignores, project).execute)
end
- def gitlab_ci_ymls(project = @project)
+ def gitlab_ci_ymls(project)
@gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls, project).execute)
end
- def dockerfile_names(project = @project)
+ def dockerfile_names(project)
@dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles, project).execute)
end
- def blob_editor_paths(project = @project)
+ def blob_editor_paths(project)
{
'relative-url-root' => Rails.application.config.relative_url_root,
'assets-prefix' => Gitlab::Application.config.assets.prefix,
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 6f9e2ef78cd..923a06a0512 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -42,7 +42,7 @@ module CiStatusHelper
when 'manual'
s_('CiStatusText|blocked')
when 'scheduled'
- s_('CiStatusText|scheduled')
+ s_('CiStatusText|delayed')
else
# All states are already being translated inside the detailed statuses:
# :running => Gitlab::Ci::Status::Running
diff --git a/app/helpers/count_helper.rb b/app/helpers/count_helper.rb
index e16223a82c9..13839474e1f 100644
--- a/app/helpers/count_helper.rb
+++ b/app/helpers/count_helper.rb
@@ -8,4 +8,18 @@ module CountHelper
number_with_delimiter(count)
end
+
+ # This will approximate the fork count by checking all counting all fork network
+ # memberships, and deducting 1 for each root of the fork network.
+ # This might be inacurate as the root of the fork network might have been deleted.
+ #
+ # This makes querying this information a lot more effecient and it should be
+ # accurate enough for the instance wide statistics
+ def approximate_fork_count_with_delimiters(count_data)
+ fork_network_count = count_data[ForkNetwork]
+ fork_network_member_count = count_data[ForkNetworkMember]
+ approximate_fork_count = fork_network_member_count - fork_network_count
+
+ number_with_delimiter(approximate_fork_count)
+ end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index f573fd399a5..0c313e9e6d3 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -1,6 +1,15 @@
# frozen_string_literal: true
module GroupsHelper
+ def group_overview_nav_link_paths
+ %w[
+ groups#show
+ groups#activity
+ groups#subgroups
+ analytics#show
+ ]
+ end
+
def group_nav_link_paths
%w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 97406fefd43..6069640b9c8 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -386,8 +386,8 @@ module IssuablesHelper
{
todo_text: "Add todo",
mark_text: "Mark todo as done",
- todo_icon: (is_collapsed ? icon('plus-square') : nil),
- mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
+ todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil),
+ mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil),
issuable_id: issuable.id,
issuable_type: issuable.class.name.underscore,
url: project_todos_path(@project),
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
index 3e6a301b77d..719c351242c 100644
--- a/app/helpers/time_helper.rb
+++ b/app/helpers/time_helper.rb
@@ -21,17 +21,15 @@ module TimeHelper
"#{from.to_s(:short)} - #{to.to_s(:short)}"
end
- def duration_in_numbers(duration_in_seconds, allow_overflow = false)
- if allow_overflow
- seconds = duration_in_seconds % 1.minute
- minutes = (duration_in_seconds / 1.minute) % (1.hour / 1.minute)
- hours = duration_in_seconds / 1.hour
+ def duration_in_numbers(duration_in_seconds)
+ seconds = duration_in_seconds % 1.minute
+ minutes = (duration_in_seconds / 1.minute) % (1.hour / 1.minute)
+ hours = duration_in_seconds / 1.hour
- "%02d:%02d:%02d" % [hours, minutes, seconds]
+ if hours == 0
+ "%02d:%02d" % [minutes, seconds]
else
- time_format = duration_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S"
-
- Time.at(duration_in_seconds).utc.strftime(time_format)
+ "%02d:%02d:%02d" % [hours, minutes, seconds]
end
end
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index cb73fc74bb6..34a889057ab 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -19,7 +19,9 @@ module Ci
sast: 'gl-sast-report.json',
dependency_scanning: 'gl-dependency-scanning-report.json',
container_scanning: 'gl-container-scanning-report.json',
- dast: 'gl-dast-report.json'
+ dast: 'gl-dast-report.json',
+ license_management: 'gl-license-management-report.json',
+ performance: 'performance.json'
}.freeze
TYPE_AND_FORMAT_PAIRS = {
@@ -27,11 +29,17 @@ module Ci
metadata: :gzip,
trace: :raw,
junit: :gzip,
- codequality: :gzip,
- sast: :gzip,
- dependency_scanning: :gzip,
- container_scanning: :gzip,
- dast: :gzip
+
+ # All these file formats use `raw` as we need to store them uncompressed
+ # for Frontend to fetch the files and do analysis
+ # When they will be only used by backend, they can be `gzipped`.
+ codequality: :raw,
+ sast: :raw,
+ dependency_scanning: :raw,
+ container_scanning: :raw,
+ dast: :raw,
+ license_management: :raw,
+ performance: :raw
}.freeze
belongs_to :project
@@ -76,7 +84,9 @@ module Ci
dependency_scanning: 6, ## EE-specific
container_scanning: 7, ## EE-specific
dast: 8, ## EE-specific
- codequality: 9 ## EE-specific
+ codequality: 9, ## EE-specific
+ license_management: 10, ## EE-specific
+ performance: 11 ## EE-specific
}
enum file_format: {
@@ -100,7 +110,8 @@ module Ci
}
FILE_FORMAT_ADAPTERS = {
- gzip: Gitlab::Ci::Build::Artifacts::GzipFileAdapter
+ gzip: Gitlab::Ci::Build::Artifacts::Adapters::GzipStream,
+ raw: Gitlab::Ci::Build::Artifacts::Adapters::RawStream
}.freeze
def valid_file_format?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 17024e8a0af..aeee7f0a5d2 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -268,6 +268,12 @@ module Ci
stage unless stage.statuses_count.zero?
end
+ def ref_exists?
+ project.repository.ref_exists?(git_ref)
+ rescue Gitlab::Git::Repository::NoRepository
+ false
+ end
+
##
# TODO We do not completely switch to persisted stages because of
# race conditions with setting statuses gitlab-ce#23257.
@@ -674,11 +680,11 @@ module Ci
def push_details
strong_memoize(:push_details) do
- Gitlab::Git::Push.new(project, before_sha, sha, push_ref)
+ Gitlab::Git::Push.new(project, before_sha, sha, git_ref)
end
end
- def push_ref
+ def git_ref
if branch?
Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
elsif tag?
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index a4a2e2b79a6..b311f5e0617 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
- VERSION = '0.1.31'.freeze
+ VERSION = '0.1.35'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 20d53b8e620..95efecfc41d 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -31,6 +31,9 @@ module Clusters
has_one :application_runner, class_name: 'Clusters::Applications::Runner'
has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter'
+ has_many :kubernetes_namespaces
+ has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace'
+
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
new file mode 100644
index 00000000000..fb5f6b65d9d
--- /dev/null
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Clusters
+ class KubernetesNamespace < ActiveRecord::Base
+ self.table_name = 'clusters_kubernetes_namespaces'
+
+ belongs_to :cluster_project, class_name: 'Clusters::Project'
+ belongs_to :cluster, class_name: 'Clusters::Cluster'
+ belongs_to :project, class_name: '::Project'
+ has_one :platform_kubernetes, through: :cluster
+
+ validates :namespace, presence: true
+ validates :namespace, uniqueness: { scope: :cluster_id }
+
+ before_validation :set_namespace_and_service_account_to_default, on: :create
+
+ attr_encrypted :service_account_token,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc'
+
+ def token_name
+ "#{namespace}-token"
+ end
+
+ private
+
+ def set_namespace_and_service_account_to_default
+ self.namespace ||= default_namespace
+ self.service_account_name ||= default_service_account_name
+ end
+
+ def default_namespace
+ platform_kubernetes&.namespace.presence || project_namespace
+ end
+
+ def default_service_account_name
+ "#{namespace}-service-account"
+ end
+
+ def project_namespace
+ Gitlab::NamespaceSanitizer.sanitize(project_slug)
+ end
+
+ def project_slug
+ "#{project.path}-#{project.id}".downcase
+ end
+ end
+end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 3a335909101..e8e943872de 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -7,6 +7,8 @@ module Clusters
include ReactiveCaching
include EnumWithNil
+ RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
+
self.table_name = 'cluster_platforms_kubernetes'
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] }
@@ -32,6 +34,8 @@ module Clusters
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
+ validates :namespace, exclusion: { in: RESERVED_NAMESPACES }
+
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
validates :api_url, url: true, presence: true
validates :token, presence: true
@@ -45,6 +49,7 @@ module Clusters
delegate :project, to: :cluster, allow_nil: true
delegate :enabled?, to: :cluster, allow_nil: true
delegate :managed?, to: :cluster, allow_nil: true
+ delegate :kubernetes_namespace, to: :cluster
alias_method :active?, :enabled?
@@ -116,10 +121,19 @@ module Clusters
end
def default_namespace
+ kubernetes_namespace&.namespace.presence || fallback_default_namespace
+ end
+
+ # DEPRECATED
+ #
+ # On 11.4 Clusters::KubernetesNamespace was introduced, this model will allow to
+ # have multiple namespaces per project. This method will be removed after migration
+ # has been completed.
+ def fallback_default_namespace
return unless project
slug = "#{project.path}-#{project.id}".downcase
- slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
+ Gitlab::NamespaceSanitizer.sanitize(slug)
end
def build_kube_client!(api_groups: ['api'], api_version: 'v1')
diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb
index 839ce796081..15092b1c9d2 100644
--- a/app/models/clusters/project.rb
+++ b/app/models/clusters/project.rb
@@ -6,5 +6,8 @@ module Clusters
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :project, class_name: '::Project'
+
+ has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
+ has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
end
end
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index 85229cded5d..045bf392ac8 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -12,6 +12,49 @@ module RelativePositioning
after_save :save_positionable_neighbours
end
+ class_methods do
+ def move_to_end(objects)
+ parent_ids = objects.map(&:parent_ids).flatten.uniq
+ max_relative_position = in_parents(parent_ids).maximum(:relative_position) || START_POSITION
+ objects = objects.reject(&:relative_position)
+
+ self.transaction do
+ objects.each do |object|
+ relative_position = position_between(max_relative_position, MAX_POSITION)
+ object.relative_position = relative_position
+ max_relative_position = relative_position
+ object.save
+ end
+ end
+ end
+
+ # This method takes two integer values (positions) and
+ # calculates the position between them. The range is huge as
+ # the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time
+ # when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number
+ def position_between(pos_before, pos_after)
+ pos_before ||= MIN_POSITION
+ pos_after ||= MAX_POSITION
+
+ pos_before, pos_after = [pos_before, pos_after].sort
+
+ halfway = (pos_after + pos_before) / 2
+ distance_to_halfway = pos_after - halfway
+
+ if distance_to_halfway < IDEAL_DISTANCE
+ halfway
+ else
+ if pos_before == MIN_POSITION
+ pos_after - IDEAL_DISTANCE
+ elsif pos_after == MAX_POSITION
+ pos_before + IDEAL_DISTANCE
+ else
+ halfway
+ end
+ end
+ end
+ end
+
def min_relative_position
self.class.in_parents(parent_ids).minimum(:relative_position)
end
@@ -57,7 +100,7 @@ module RelativePositioning
@positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
- self.relative_position = position_between(before.relative_position, after.relative_position)
+ self.relative_position = self.class.position_between(before.relative_position, after.relative_position)
end
def move_after(before = self)
@@ -72,7 +115,7 @@ module RelativePositioning
pos_after = issue_to_move.relative_position
end
- self.relative_position = position_between(pos_before, pos_after)
+ self.relative_position = self.class.position_between(pos_before, pos_after)
end
def move_before(after = self)
@@ -87,15 +130,15 @@ module RelativePositioning
pos_before = issue_to_move.relative_position
end
- self.relative_position = position_between(pos_before, pos_after)
+ self.relative_position = self.class.position_between(pos_before, pos_after)
end
def move_to_end
- self.relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION)
+ self.relative_position = self.class.position_between(max_relative_position || START_POSITION, MAX_POSITION)
end
def move_to_start
- self.relative_position = position_between(min_relative_position || START_POSITION, MIN_POSITION)
+ self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION)
end
# Indicates if there is an issue that should be shifted to free the place
@@ -112,32 +155,6 @@ module RelativePositioning
private
- # This method takes two integer values (positions) and
- # calculates the position between them. The range is huge as
- # the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time
- # when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number
- def position_between(pos_before, pos_after)
- pos_before ||= MIN_POSITION
- pos_after ||= MAX_POSITION
-
- pos_before, pos_after = [pos_before, pos_after].sort
-
- halfway = (pos_after + pos_before) / 2
- distance_to_halfway = pos_after - halfway
-
- if distance_to_halfway < IDEAL_DISTANCE
- halfway
- else
- if pos_before == MIN_POSITION
- pos_after - IDEAL_DISTANCE
- elsif pos_after == MAX_POSITION
- pos_before + IDEAL_DISTANCE
- else
- halfway
- end
- end
- end
-
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def save_positionable_neighbours
return unless @positionable_neighbours
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
new file mode 100644
index 00000000000..5ff3acc0e58
--- /dev/null
+++ b/app/models/environment_status.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+class EnvironmentStatus
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :environment, :merge_request
+
+ delegate :id, to: :environment
+ delegate :name, to: :environment
+ delegate :project, to: :environment
+ delegate :deployed_at, to: :deployment, allow_nil: true
+
+ def initialize(environment, merge_request)
+ @environment = environment
+ @merge_request = merge_request
+ end
+
+ def deployment
+ strong_memoize(:deployment) do
+ environment.first_deployment_for(merge_request.diff_head_sha)
+ end
+ end
+
+ def deployed_at
+ deployment&.created_at
+ end
+
+ def changes
+ sha = merge_request.diff_head_sha
+ return [] if project.route_map_for(sha).nil?
+
+ changed_files.map { |file| build_change(file, sha) }.compact
+ end
+
+ def changed_files
+ merge_request.merge_request_diff
+ .merge_request_diff_files.where(deleted_file: false)
+ end
+
+ private
+
+ PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
+
+ def build_change(file, sha)
+ public_path = project.public_path_for_source_path(file.new_path, sha)
+ return if public_path.nil?
+
+ ext = File.extname(public_path)
+ return if ext.present? && ext !~ PAGE_EXTENSIONS
+
+ {
+ path: public_path,
+ external_url: environment.external_url_for(file.new_path, sha)
+ }
+ end
+end
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
deleted file mode 100644
index 0f7067238cd..00000000000
--- a/app/models/forked_project_link.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-class ForkedProjectLink < ActiveRecord::Base
- belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
- belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
-end
diff --git a/app/models/list.rb b/app/models/list.rb
index 1a30acc83cf..029685be927 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -15,6 +15,7 @@ class List < ActiveRecord::Base
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
+ scope :preload_associations, -> { preload(:board, :label) }
class << self
def destroyable_types
diff --git a/app/models/note.rb b/app/models/note.rb
index 95e1d3afa00..e1bd943e8e4 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -110,6 +110,15 @@ class Note < ActiveRecord::Base
:system_note_metadata, :note_diff_file)
end
+ scope :with_notes_filter, -> (notes_filter) do
+ case notes_filter
+ when UserPreference::NOTES_FILTERS[:only_comments]
+ user
+ else
+ all
+ end
+ end
+
scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) }
scope :new_diff_notes, -> { where(type: 'DiffNote') }
scope :non_diff_notes, -> { where(type: ['Note', 'DiscussionNote', nil]) }
diff --git a/app/models/project.rb b/app/models/project.rb
index b80e41e4a96..382fb4f463a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -167,20 +167,15 @@ class Project < ActiveRecord::Base
has_one :packagist_service
has_one :hangouts_chat_service
- # TODO: replace these relations with the fork network versions
- has_one :forked_project_link, foreign_key: "forked_to_project_id"
- has_one :forked_from_project, through: :forked_project_link
-
- has_many :forked_project_links, foreign_key: "forked_from_project_id"
- has_many :forks, through: :forked_project_links, source: :forked_to_project
- # TODO: replace these relations with the fork network versions
-
has_one :root_of_fork_network,
foreign_key: 'root_project_id',
inverse_of: :root_project,
class_name: 'ForkNetwork'
has_one :fork_network_member
has_one :fork_network, through: :fork_network_member
+ has_one :forked_from_project, through: :fork_network_member
+ has_many :forked_to_members, class_name: 'ForkNetworkMember', foreign_key: 'forked_from_project_id'
+ has_many :forks, through: :forked_to_members, source: :project, inverse_of: :forked_from_project
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -553,6 +548,8 @@ class Project < ActiveRecord::Base
self[:lfs_enabled] && Gitlab.config.lfs.enabled
end
+ alias_method :lfs_enabled, :lfs_enabled?
+
def auto_devops_enabled?
if auto_devops&.enabled.nil?
has_auto_devops_implicitly_enabled?
@@ -693,6 +690,8 @@ class Project < ActiveRecord::Base
else
super
end
+ rescue
+ super
end
def valid_import_url?
@@ -1250,12 +1249,7 @@ class Project < ActiveRecord::Base
end
def forked?
- return true if fork_network && fork_network.root_project != self
-
- # TODO: Use only the above conditional using the `fork_network`
- # This is the old conditional that looks at the `forked_project_link`, we
- # fall back to this while we're migrating the new models
- !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
+ fork_network && fork_network.root_project != self
end
def fork_source
@@ -1546,9 +1540,7 @@ class Project < ActiveRecord::Base
def visibility_level_allowed_as_fork?(level = self.visibility_level)
return true unless forked?
- # self.forked_from_project will be nil before the project is saved, so
- # we need to go through the relation
- original_project = forked_project_link&.forked_from_project
+ original_project = fork_source
return true unless original_project
level <= original_project.visibility_level
@@ -1640,34 +1632,6 @@ class Project < ActiveRecord::Base
end
# rubocop: enable CodeReuse/ServiceClass
- def rename_repo
- path_before = previous_changes['path'].first
- full_path_before = full_path_was
- full_path_after = build_full_path
-
- Gitlab::AppLogger.info("Attempting to rename #{full_path_was} -> #{full_path_after}")
-
- if has_container_registry_tags?
- Gitlab::AppLogger.info("Project #{full_path_was} cannot be renamed because container registry tags are present!")
-
- # we currently don't support renaming repository if it contains images in container registry
- raise StandardError.new('Project cannot be renamed, because images are present in its container registry')
- end
-
- expire_caches_before_rename(full_path_before)
-
- if rename_or_migrate_repository!
- Gitlab::AppLogger.info("Project was renamed: #{full_path_before} -> #{full_path_after}")
- after_rename_repository(full_path_before, path_before)
- else
- Gitlab::AppLogger.info("Repository could not be renamed: #{full_path_before} -> #{full_path_after}")
-
- # if we cannot move namespace directory we should rollback
- # db changes in order to prevent out of sync between db and fs
- raise StandardError.new('Repository cannot be renamed')
- end
- end
-
def write_repository_config(gl_full_path: full_path)
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
@@ -2096,51 +2060,6 @@ class Project < ActiveRecord::Base
auto_cancel_pending_pipelines == 'enabled'
end
- private
-
- # rubocop: disable CodeReuse/ServiceClass
- def rename_or_migrate_repository!
- if Gitlab::CurrentSettings.hashed_storage_enabled? &&
- storage_upgradable? &&
- Feature.disabled?(:skip_hashed_storage_upgrade) # kill switch in case we need to disable upgrade behavior
- ::Projects::HashedStorageMigrationService.new(self, full_path_was).execute
- else
- storage.rename_repo
- end
- end
- # rubocop: enable CodeReuse/ServiceClass
-
- def storage_upgradable?
- storage_version != LATEST_STORAGE_VERSION
- end
-
- def after_rename_repository(full_path_before, path_before)
- execute_rename_repository_hooks!(full_path_before)
-
- write_repository_config
-
- # We need to check if project had been rolled out to move resource to hashed storage or not and decide
- # if we need execute any take action or no-op.
- unless hashed_storage?(:attachments)
- Gitlab::UploadsTransfer.new.rename_project(path_before, self.path, namespace.full_path)
- end
-
- Gitlab::PagesTransfer.new.rename_project(path_before, self.path, namespace.full_path)
- end
-
- # rubocop: disable CodeReuse/ServiceClass
- def execute_rename_repository_hooks!(full_path_before)
- # When we import a project overwriting the original project, there
- # is a move operation. In that case we don't want to send the instructions.
- send_move_instructions(full_path_before) unless import_started?
-
- self.old_path_with_namespace = full_path_before
- SystemHooksService.new.execute_hooks_for(self, :rename)
-
- reload_repository!
- end
- # rubocop: enable CodeReuse/ServiceClass
-
def storage
@storage ||=
if hashed_storage?(:repository)
@@ -2150,6 +2069,12 @@ class Project < ActiveRecord::Base
end
end
+ def storage_upgradable?
+ storage_version != LATEST_STORAGE_VERSION
+ end
+
+ private
+
def use_hashed_storage
if self.new_record? && Gitlab::CurrentSettings.hashed_storage_enabled
self.storage_version = LATEST_STORAGE_VERSION
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d502423726c..d121d088ff6 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -80,13 +80,18 @@ class BambooService < CiService
private
+ def get_build_result_index
+ # When Bamboo returns multiple results for a given changeset, arbitrarily assume the most relevant result to be the last one.
+ -1
+ end
+
def read_build_page(response)
- if response.code != 200 || response['results']['results']['size'] == '0'
+ if response.code != 200 || response.dig('results', 'results', 'size') == '0'
# If actual build link can't be determined, send user to build summary page.
URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
else
# If actual build link is available, go to build result page.
- result_key = response['results']['results']['result']['planResultKey']['key']
+ result_key = response.dig('results', 'results', 'result', get_build_result_index, 'planResultKey', 'key')
URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
end
end
@@ -94,10 +99,10 @@ class BambooService < CiService
def read_commit_status(response)
return :error unless response.code == 200 || response.code == 404
- status = if response.code == 404 || response['results']['results']['size'] == '0'
+ status = if response.code == 404 || response.dig('results', 'results', 'size') == '0'
'Pending'
else
- response['results']['results']['result']['buildState']
+ response.dig('results', 'results', 'result', get_build_result_index, 'buildState')
end
if status.include?('Success')
diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb
index 5b0e5fed092..c34078f13c1 100644
--- a/app/models/project_services/microsoft_teams_service.rb
+++ b/app/models/project_services/microsoft_teams_service.rb
@@ -17,7 +17,7 @@ class MicrosoftTeamsService < ChatNotificationService
'This service sends notifications about projects events to Microsoft Teams channels.<br />
To set up this service:
<ol>
- <li><a href="https://msdn.microsoft.com/en-us/microsoft-teams/connectors">Getting started with 365 Office Connectors For Microsoft Teams</a>.</li>
+ <li><a href="https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors/connectors-using#setting-up-a-custom-incoming-webhook">Setup a custom Incoming Webhook using Office 365 Connectors For Microsoft Teams</a>.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications.</li>
</ol>'
diff --git a/app/models/user.rb b/app/models/user.rb
index a0665518cf5..ca7fc3b058f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -152,6 +152,7 @@ class User < ActiveRecord::Base
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
has_one :status, class_name: 'UserStatus'
+ has_one :user_preference
#
# Validations
@@ -224,6 +225,8 @@ class User < ActiveRecord::Base
enum project_view: [:readme, :activity, :files]
delegate :path, to: :namespace, allow_nil: true, prefix: true
+ delegate :notes_filter_for, to: :user_preference
+ delegate :set_notes_filter, to: :user_preference
state_machine :state, initial: :active do
event :block do
@@ -264,7 +267,7 @@ class User < ActiveRecord::Base
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
- scope :by_username, -> (usernames) { iwhere(username: usernames) }
+ scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) }
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
# Limits the users to those that have TODOs, optionally in the given state.
@@ -1367,6 +1370,11 @@ class User < ActiveRecord::Base
!consented_usage_stats? && 7.days.ago > self.created_at && !has_current_license? && User.single_user?
end
+ # Avoid migrations only building user preference object when needed.
+ def user_preference
+ super.presence || build_user_preference
+ end
+
def todos_limited_to(ids)
todos.where(id: ids)
end
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
new file mode 100644
index 00000000000..6cd91abc261
--- /dev/null
+++ b/app/models/user_preference.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class UserPreference < ActiveRecord::Base
+ # We could use enums, but Rails 4 doesn't support multiple
+ # enum options with same name for multiple fields, also it creates
+ # extra methods that aren't really needed here.
+ NOTES_FILTERS = { all_notes: 0, only_comments: 1 }.freeze
+
+ belongs_to :user
+
+ validates :issue_notes_filter, :merge_request_notes_filter, inclusion: { in: NOTES_FILTERS.values }, presence: true
+
+ class << self
+ def notes_filters
+ {
+ s_('Notes|Show all activity') => NOTES_FILTERS[:all_notes],
+ s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments]
+ }
+ end
+ end
+
+ def set_notes_filter(filter_id, issuable)
+ # No need to update the column if the value is already set.
+ if filter_id && NOTES_FILTERS.values.include?(filter_id)
+ field = notes_filter_field_for(issuable)
+ self[field] = filter_id
+
+ save if attribute_changed?(field)
+ end
+
+ notes_filter_for(issuable)
+ end
+
+ # Returns the current discussion filter for a given issuable
+ # or issuable type.
+ def notes_filter_for(resource)
+ self[notes_filter_field_for(resource)]
+ end
+
+ private
+
+ def notes_filter_field_for(resource)
+ field_key =
+ if resource.is_a?(Issuable)
+ resource.model_name.param_key
+ else
+ resource
+ end
+
+ "#{field_key}_notes_filter"
+ end
+end
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 880218e2727..300f85e1e9d 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -30,12 +30,12 @@ module Ci
def create_reports(reports, expire_in:)
return unless reports&.any?
- reports.map do |k, v|
+ reports.map do |report_type, report_paths|
{
- artifact_type: k.to_sym,
- artifact_format: :gzip,
- name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES[k.to_sym],
- paths: v,
+ artifact_type: report_type.to_sym,
+ artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(report_type.to_sym),
+ name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(report_type.to_sym),
+ paths: report_paths,
when: 'always',
expire_in: expire_in
}
diff --git a/app/serializers/current_user_entity.rb b/app/serializers/current_user_entity.rb
new file mode 100644
index 00000000000..71d14e727dd
--- /dev/null
+++ b/app/serializers/current_user_entity.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# Always use this entity when rendering data for current user
+# for attributes that does not need to be visible to other users
+# like user preferences.
+class CurrentUserEntity < UserEntity
+ expose :user_preference, using: UserPreferenceEntity
+end
diff --git a/app/serializers/environment_status_entity.rb b/app/serializers/environment_status_entity.rb
new file mode 100644
index 00000000000..3dfa4f204c9
--- /dev/null
+++ b/app/serializers/environment_status_entity.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+class EnvironmentStatusEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id
+ expose :name
+
+ expose :url do |es|
+ project_environment_path(es.project, es.environment)
+ end
+
+ expose :metrics_url, if: ->(*) { can_read_environment? && environment.has_metrics? } do |es|
+ metrics_project_environment_deployment_path(es.project, es.environment, es.deployment)
+ end
+
+ expose :metrics_monitoring_url, if: ->(*) { can_read_environment? } do |es|
+ environment_metrics_path(es.environment)
+ end
+
+ expose :stop_url, if: ->(*) { can_stop_environment? } do |es|
+ stop_project_environment_path(es.project, es.environment)
+ end
+
+ expose :external_url do |es|
+ es.environment.external_url
+ end
+
+ expose :external_url_formatted do |es|
+ es.environment.formatted_external_url
+ end
+
+ expose :deployed_at
+
+ expose :deployed_at_formatted do |es|
+ es.deployment.try(:formatted_deployment_time)
+ end
+
+ expose :changes, if: ->(*) { Feature.enabled?(:ci_environments_status_changes, project) }
+
+ private
+
+ def environment
+ object.environment
+ end
+
+ def project
+ object.environment.project
+ end
+
+ def current_user
+ request.current_user
+ end
+
+ def can_read_environment?
+ can?(current_user, :read_environment, environment)
+ end
+
+ def can_stop_environment?
+ can?(current_user, :stop_environment, environment)
+ end
+end
diff --git a/app/serializers/environment_status_serializer.rb b/app/serializers/environment_status_serializer.rb
new file mode 100644
index 00000000000..f8d37934763
--- /dev/null
+++ b/app/serializers/environment_status_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class EnvironmentStatusSerializer < BaseSerializer
+ entity EnvironmentStatusEntity
+end
diff --git a/app/serializers/merge_request_user_entity.rb b/app/serializers/merge_request_user_entity.rb
index fd2d2897113..53257b0602c 100644
--- a/app/serializers/merge_request_user_entity.rb
+++ b/app/serializers/merge_request_user_entity.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestUserEntity < UserEntity
+class MergeRequestUserEntity < CurrentUserEntity
include RequestAwareEntity
include BlobHelper
include TreeHelper
diff --git a/app/serializers/user_preference_entity.rb b/app/serializers/user_preference_entity.rb
new file mode 100644
index 00000000000..fbdaab459b3
--- /dev/null
+++ b/app/serializers/user_preference_entity.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class UserPreferenceEntity < Grape::Entity
+ expose :issue_notes_filter
+ expose :merge_request_notes_filter
+
+ expose :notes_filters do |user_preference|
+ UserPreference.notes_filters
+ end
+end
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
index 4c5e22bdd7e..201048aaba5 100644
--- a/app/services/audit_event_service.rb
+++ b/app/services/audit_event_service.rb
@@ -17,11 +17,29 @@ class AuditEventService
end
def security_event
- SecurityEvent.create(
+ log_security_event_to_file
+ log_security_event_to_database
+ end
+
+ private
+
+ def base_payload
+ {
author_id: @author.id,
entity_id: @entity.id,
- entity_type: @entity.class.name,
- details: @details
- )
+ entity_type: @entity.class.name
+ }
+ end
+
+ def file_logger
+ @file_logger ||= Gitlab::AuditJsonLogger.build
+ end
+
+ def log_security_event_to_file
+ file_logger.info(base_payload.merge(@details))
+ end
+
+ def log_security_event_to_database
+ SecurityEvent.create(base_payload.merge(details: @details))
end
end
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
index e10eb52e041..5cf5f14a55b 100644
--- a/app/services/boards/lists/list_service.rb
+++ b/app/services/boards/lists/list_service.rb
@@ -6,7 +6,7 @@ module Boards
def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
- board.lists
+ board.lists.preload_associations
end
end
end
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index fe47aa2f140..0bf0e967dcc 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -14,9 +14,11 @@ module Groups
group.assign_attributes(params)
begin
- after_update if group.save
+ success = group.save
- true
+ after_update if success
+
+ success
rescue Gitlab::UpdatePathError => e
group.errors.add(:base, e.message)
diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb
new file mode 100644
index 00000000000..4131da44f5a
--- /dev/null
+++ b/app/services/projects/after_rename_service.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+module Projects
+ # Service class for performing operations that should take place after a
+ # project has been renamed.
+ #
+ # Example usage:
+ #
+ # project = Project.find(42)
+ #
+ # project.update(...)
+ #
+ # Projects::AfterRenameService.new(project).execute
+ class AfterRenameService
+ attr_reader :project, :full_path_before, :full_path_after, :path_before
+
+ RenameFailedError = Class.new(StandardError)
+
+ # @param [Project] project The Project of the repository to rename.
+ def initialize(project)
+ @project = project
+
+ # The full path of the namespace + project, before the rename took place.
+ @full_path_before = project.full_path_was
+
+ # The full path of the namespace + project, after the rename took place.
+ @full_path_after = project.build_full_path
+
+ # The path of just the project, before the rename took place.
+ @path_before = project.path_was
+ end
+
+ def execute
+ first_ensure_no_registry_tags_are_present
+ expire_caches_before_rename
+ rename_or_migrate_repository!
+ send_move_instructions
+ execute_system_hooks
+ update_repository_configuration
+ rename_transferred_documents
+ log_completion
+ end
+
+ def first_ensure_no_registry_tags_are_present
+ return unless project.has_container_registry_tags?
+
+ raise RenameFailedError.new(
+ "Project #{full_path_before} cannot be renamed because images are " \
+ "present in its container registry"
+ )
+ end
+
+ def expire_caches_before_rename
+ project.expire_caches_before_rename(full_path_before)
+ end
+
+ def rename_or_migrate_repository!
+ success =
+ if migrate_to_hashed_storage?
+ ::Projects::HashedStorageMigrationService
+ .new(project, full_path_before)
+ .execute
+ else
+ project.storage.rename_repo
+ end
+
+ rename_failed! unless success
+ end
+
+ def send_move_instructions
+ return unless send_move_instructions?
+
+ project.send_move_instructions(full_path_before)
+ end
+
+ def execute_system_hooks
+ project.old_path_with_namespace = full_path_before
+ SystemHooksService.new.execute_hooks_for(project, :rename)
+ end
+
+ def update_repository_configuration
+ project.reload_repository!
+ project.write_repository_config
+ end
+
+ def rename_transferred_documents
+ if rename_uploads?
+ Gitlab::UploadsTransfer
+ .new
+ .rename_project(path_before, project_path, namespace_full_path)
+ end
+
+ Gitlab::PagesTransfer
+ .new
+ .rename_project(path_before, project_path, namespace_full_path)
+ end
+
+ def log_completion
+ Gitlab::AppLogger.info(
+ "Project #{project.id} has been renamed from " \
+ "#{full_path_before} to #{full_path_after}"
+ )
+ end
+
+ def migrate_to_hashed_storage?
+ Gitlab::CurrentSettings.hashed_storage_enabled? &&
+ project.storage_upgradable? &&
+ Feature.disabled?(:skip_hashed_storage_upgrade)
+ end
+
+ def send_move_instructions?
+ !project.import_started?
+ end
+
+ def rename_uploads?
+ !project.hashed_storage?(:attachments)
+ end
+
+ def project_path
+ project.path
+ end
+
+ def namespace_full_path
+ project.namespace.full_path
+ end
+
+ def rename_failed!
+ error = "Repository #{full_path_before} could not be renamed to #{full_path_after}"
+
+ Gitlab::AppLogger.error(error)
+
+ raise RenameFailedError.new(error)
+ end
+ end
+end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 0e6a7e8da54..20bfe5af7a1 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -13,8 +13,8 @@ module Projects
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
- forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
+ relations_block = params.delete(:relations_block)
@project = Project.new(params)
@@ -24,11 +24,6 @@ module Projects
return @project
end
- unless allowed_fork?(forked_from_project_id)
- @project.errors.add(:forked_from_project_id, 'is forbidden')
- return @project
- end
-
set_project_name_from_path
# get namespace id
@@ -47,6 +42,7 @@ module Projects
@project.namespace_id = current_user.namespace_id
end
+ relations_block&.call(@project)
yield(@project) if block_given?
# If the block added errors, don't try to save the project
@@ -54,10 +50,6 @@ module Projects
@project.creator = current_user
- if forked_from_project_id
- @project.build_forked_project_link(forked_from_project_id: forked_from_project_id)
- end
-
save_project_and_import_data(import_data)
after_create_actions if @project.persisted?
@@ -80,15 +72,6 @@ module Projects
end
# rubocop: disable CodeReuse/ActiveRecord
- def allowed_fork?(source_project_id)
- return true if source_project_id.nil?
-
- source_project = Project.find_by(id: source_project_id)
- current_user.can?(:fork_project, source_project)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # rubocop: disable CodeReuse/ActiveRecord
def allowed_namespace?(user, namespace_id)
namespace = Namespace.find_by(id: namespace_id)
current_user.can?(:create_projects, namespace)
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index cbbb88a9410..8dc0e044875 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -12,31 +12,42 @@ module Projects
private
+ def allowed_fork?
+ current_user.can?(:fork_project, @project)
+ end
+
def link_existing_project(fork_to_project)
return if fork_to_project.forked?
- link_fork_network(fork_to_project)
+ build_fork_network_member(fork_to_project)
- # A forked project stores its LFS objects in the `forked_from_project`.
- # So the LFS objects become inaccessible, and therefore delete them from
- # the database so they'll get cleaned up.
- #
- # TODO: refactor this to get the correct lfs objects when implementing
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
- fork_to_project.lfs_objects_projects.delete_all
+ if link_fork_network(fork_to_project)
+ # A forked project stores its LFS objects in the `forked_from_project`.
+ # So the LFS objects become inaccessible, and therefore delete them from
+ # the database so they'll get cleaned up.
+ #
+ # TODO: refactor this to get the correct lfs objects when implementing
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
+ fork_to_project.lfs_objects_projects.delete_all
- fork_to_project
+ fork_to_project
+ end
end
def fork_new_project
new_params = {
- forked_from_project_id: @project.id,
visibility_level: allowed_visibility_level,
description: @project.description,
name: @project.name,
path: @project.path,
shared_runners_enabled: @project.shared_runners_enabled,
- namespace_id: target_namespace.id
+ namespace_id: target_namespace.id,
+ fork_network: fork_network,
+ # We need to assign the fork network membership after the project has
+ # been instantiated to avoid ActiveRecord trying to create it when
+ # initializing the project, as that would cause a foreign key constraint
+ # exception.
+ relations_block: -> (project) { build_fork_network_member(project) }
}
if @project.avatar.present? && @project.avatar.image?
@@ -46,38 +57,35 @@ module Projects
new_project = CreateService.new(current_user, new_params).execute
return new_project unless new_project.persisted?
+ # Set the forked_from_project relation after saving to avoid having to
+ # reload the project to reset the association information and cause an
+ # extra query.
+ new_project.forked_from_project = @project
+
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update(builds_access_level: builds_access_level)
- link_fork_network(new_project)
-
new_project
end
def fork_network
- if @project.fork_network
- @project.fork_network
- elsif forked_from_project = @project.forked_from_project
- # TODO: remove this case when all background migrations have completed
- # this only happens when a project had a `forked_project_link` that was
- # not migrated to the `fork_network` relation
- forked_from_project.fork_network || forked_from_project.create_root_of_fork_network
+ @fork_network ||= @project.fork_network || @project.build_root_of_fork_network
+ end
+
+ def build_fork_network_member(fork_to_project)
+ if allowed_fork?
+ fork_to_project.build_fork_network_member(forked_from_project: @project,
+ fork_network: fork_network)
else
- @project.create_root_of_fork_network
+ fork_to_project.errors.add(:forked_from_project_id, 'is forbidden')
end
end
def link_fork_network(fork_to_project)
- fork_network.fork_network_members.create(project: fork_to_project,
- forked_from_project: @project)
-
- # TODO: remove this when ForkedProjectLink model is removed
- unless fork_to_project.forked_project_link
- fork_to_project.create_forked_project_link(forked_to_project: fork_to_project,
- forked_from_project: @project)
- end
+ return if fork_to_project.errors.any?
- refresh_forks_count
+ fork_to_project.fork_network_member.save &&
+ refresh_forks_count
end
def refresh_forks_count
diff --git a/app/services/projects/forks_count_service.rb b/app/services/projects/forks_count_service.rb
index 00e73148358..ca85e2dc281 100644
--- a/app/services/projects/forks_count_service.rb
+++ b/app/services/projects/forks_count_service.rb
@@ -9,10 +9,7 @@ module Projects
# rubocop: disable CodeReuse/ActiveRecord
def self.query(project_ids)
- # We can't directly change ForkedProjectLink to ForkNetworkMember here
- # Nowadays, when a call using v3 to projects/:id/fork is made,
- # the relationship to ForkNetworkMember is not updated
- ForkedProjectLink.where(forked_from_project: project_ids)
+ ForkNetworkMember.where(forked_from_project: project_ids)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb
index 2948555a17c..33f0bab12c9 100644
--- a/app/services/projects/move_forks_service.rb
+++ b/app/services/projects/move_forks_service.rb
@@ -6,7 +6,6 @@ module Projects
return unless super && source_project.fork_network
Project.transaction(requires_new: true) do
- move_forked_project_links
move_fork_network_members
update_root_project
refresh_forks_count
@@ -18,18 +17,6 @@ module Projects
private
# rubocop: disable CodeReuse/ActiveRecord
- def move_forked_project_links
- # Update ancestor
- ForkedProjectLink.where(forked_to_project: source_project)
- .update_all(forked_to_project_id: @project.id)
-
- # Update the descendants
- ForkedProjectLink.where(forked_from_project: source_project)
- .update_all(forked_from_project_id: @project.id)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # rubocop: disable CodeReuse/ActiveRecord
def move_fork_network_members
ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)
ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id)
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index a8b7c7f136a..1b8a920268f 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -25,7 +25,6 @@ module Projects
end
@project.fork_network_member.destroy
- @project.forked_project_link.destroy
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index f25a4e30938..93e48fc0199 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -67,7 +67,7 @@ module Projects
end
if project.previous_changes.include?('path')
- project.rename_repo
+ AfterRenameService.new(project).execute
else
system_hook_service.execute_hooks_for(project, :update)
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index defa579f9a8..751aae2696d 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -643,7 +643,7 @@ module QuickActions
if users.empty?
users =
- if params == 'me'
+ if params.strip == 'me'
[current_user]
else
User.where(username: params.split(' ').map(&:strip))
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 34724e0250d..1fee8bfcd31 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -22,7 +22,7 @@ class WebHookService
end
def execute
- start_time = Time.now
+ start_time = Gitlab::Metrics::System.monotonic_time
response = if parsed_url.userinfo.blank?
make_request(hook.url)
@@ -35,7 +35,7 @@ class WebHookService
url: hook.url,
request_data: data,
response: response,
- execution_duration: Time.now - start_time
+ execution_duration: Gitlab::Metrics::System.monotonic_time - start_time
)
{
@@ -43,13 +43,13 @@ class WebHookService
http_status: response.code,
message: response.to_s
}
- rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError => e
+ rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep => e
log_execution(
trigger: hook_name,
url: hook.url,
request_data: data,
response: InternalErrorResponse.new,
- execution_duration: Time.now - start_time,
+ execution_duration: Gitlab::Metrics::System.monotonic_time - start_time,
error_message: e.to_s
)
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 85c04f8a01d..7ac79cc77f5 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -42,7 +42,7 @@
%p
Forks
%span.light.float-right
- = approximate_count_with_delimiters(@counts, ForkedProjectLink)
+ = approximate_fork_count_with_delimiters(@counts)
%p
Issues
%span.light.float-right
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 0c683f86252..5f205d1bcbc 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -63,10 +63,9 @@
.card
.card-header
- %h3.card-title
- = _('Projects')
- %span.badge.badge-pill
- #{@group.projects.count}
+ = _('Projects')
+ %span.badge.badge-pill
+ #{@group.projects.count}
%ul.content-list
- @projects.each do |project|
%li
@@ -119,7 +118,7 @@
= _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name }
%span.badge.badge-pill= @group.members.size
.float-right
- = link_to icon('pencil-square-o', text: _('Manage access')), polymorphic_url([@group, :members]), class: "btn btn-sm"
+ = link_to icon('pencil-square-o', text: _('Manage access')), group_group_members_path(@group), class: "btn btn-sm"
%ul.content-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.card-footer
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index fefb4c7455d..03cce4745aa 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -183,7 +183,7 @@
project members
%span.badge.badge-pill= @project.users.size
.float-right
- = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-sm"
+ = link_to icon('pencil-square-o', text: 'Manage access'), project_project_members_path(@project), class: "btn btn-sm"
%ul.content-list.project_members.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.card-footer
diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml
index 7503548fa3d..ec1a3fef435 100644
--- a/app/views/dashboard/_activity_head.html.haml
+++ b/app/views/dashboard/_activity_head.html.haml
@@ -1,3 +1,6 @@
+.page-title-holder
+ %h1.page-title= _('Activity')
+
.top-area
%ul.nav-links.nav.nav-tabs
%li{ class: active_when(params[:filter].nil?) }>
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 727784141bb..8ab5dc37f34 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,3 +1,10 @@
+.page-title-holder
+ %h1.page-title= _('Groups')
+
+ - if current_user.can_create_group?
+ .page-title-controls
+ = link_to _("New group"), new_group_path, class: "btn btn-success"
+
.top-area
%ul.nav-links.mobile-separator.nav.nav-tabs
= nav_link(page: dashboard_groups_path) do
@@ -9,5 +16,3 @@
.nav-controls
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
- - if current_user.can_create_group?
- = link_to _("New group"), new_group_path, class: "btn btn-success"
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 69a2e408073..1050945b15a 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,6 +1,13 @@
= content_for :flash_message do
= render 'shared/project_limit'
+.page-title-holder
+ %h1.page-title= _('Projects')
+
+ - if current_user.can_create_project?
+ .page-title-controls
+ = link_to "New project", new_project_path, class: "btn btn-success"
+
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
@@ -18,5 +25,3 @@
.nav-controls
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
- - if current_user.can_create_project?
- = link_to "New project", new_project_path, class: "btn btn-success"
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index 4f38339b87a..8d99f84755a 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -1,3 +1,10 @@
+.page-title-holder
+ %h1.page-title= _('Snippets')
+
+ - if current_user
+ .page-title-controls
+ = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet"
+
.top-area
%ul.nav-links.nav.nav-tabs
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
@@ -6,7 +13,3 @@
= nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore snippets
-
- - if current_user
- .nav-controls.d-none.d-sm-block
- = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet"
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 86a21e24ac9..832ba877558 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,11 +4,17 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
+.page-title-holder
+ %h1.page-title= _('Issues')
+
+ - if current_user
+ .page-title-controls
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
+
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
= render 'shared/issuable/feed_buttons'
- = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/issuable/filter', type: :issues
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 61aae31be60..fba8d1cf667 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,10 +2,15 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id)
+.page-title-holder
+ %h1.page-title= _('Merge Requests')
+
+ - if current_user
+ .page-title-controls
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
+
.top-area
= render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
- .nav-controls
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index f66e2b40d76..ae0e38bf0ee 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -2,12 +2,18 @@
- page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path
+.page-title-holder
+ %h1.page-title= _('Milestones')
+
+ - if current_user
+ .page-title-controls
+ = render 'shared/new_project_item_select',
+ path: 'milestones/new', label: 'New milestone',
+ include_groups: true, type: :milestones
+
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls
- = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
-
.milestones
%ul.content-list
- if @milestones.blank?
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 8b3974d97f8..d2593179f17 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,6 +2,9 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
+.page-title-holder
+ %h1.page-title= _('Todos')
+
- if current_user.todos.any?
.top-area
%ul.nav-links.mobile-separator.nav.nav-tabs
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index f3792c5e397..869c54d89ea 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -37,6 +37,8 @@
.settings-content
= render 'shared/badges/badge_settings'
+= render_if_exists 'groups/templates_setting', expanded: expanded
+
%section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 86c5f6a7aa3..684b51b8552 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,5 +1,4 @@
-- @breadcrumb_link = dashboard_groups_path
-- breadcrumb_title "Groups"
+- @hide_breadcrumbs = true
- @hide_top_links = true
- page_title 'New Group'
- header_title "Groups", dashboard_groups_path
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index 8bd5708d490..2cdaa85bdaa 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -6,5 +6,5 @@
-# Don't show a flash message if the message is nil
- if value
%div{ class: "flash-#{key}" }
- %div{ class: "#{container_class} #{extra_flash_class}" }
+ %div{ class: "#{(container_class unless fluid_layout)} #{(extra_flash_class unless @no_container)} #{@content_class}" }
%span= value
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index a41d30da450..1b2a4cd6780 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -6,12 +6,13 @@
.mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
- = render 'layouts/header/read_only_banner'
+ = render "layouts/header/read_only_banner"
= yield :flash_message
= render "shared/ping_consent"
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
- = render "layouts/flash"
+ = render "layouts/flash", extra_flash_class: 'limit-container-width'
+ .d-flex
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
= yield
diff --git a/app/views/layouts/dashboard.html.haml b/app/views/layouts/dashboard.html.haml
index 489ef245a4d..c10be282952 100644
--- a/app/views/layouts/dashboard.html.haml
+++ b/app/views/layouts/dashboard.html.haml
@@ -1,5 +1,6 @@
- page_title _("Dashboard")
- header_title _("Dashboard"), root_path unless header_title
- sidebar "dashboard"
+- @hide_breadcrumbs = true
= render template: "layouts/application"
diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml
index 80bda34a3f5..24751ab4e06 100644
--- a/app/views/layouts/explore.html.haml
+++ b/app/views/layouts/explore.html.haml
@@ -1,4 +1,6 @@
- page_title _("Explore")
+- @hide_breadcrumbs = true
+
- unless current_user
- header_title _("Explore GitLab"), explore_root_path
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 261d758622b..4f3e4031fe3 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -6,9 +6,11 @@
= current_user.name
= current_user.to_reference
- if current_user.status
- .user-status-emoji.str-truncated.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
- = emoji_icon current_user.status.emoji
- = current_user.status.message_html.html_safe
+ .user-status.d-flex.align-items-center.prepend-top-2.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
+ %span.user-status-emoji.d-flex.align-items-center
+ = emoji_icon current_user.status.emoji
+ %span.user-status-message.str-truncated
+ = current_user.status.message_html.html_safe
%li.divider
- if can?(current_user, :update_user_status, current_user)
%li
@@ -19,12 +21,7 @@
- if current_user_menu?(:settings)
%li
= link_to s_("CurrentUser|Settings"), profile_path
- - if current_user_menu?(:help)
- %li
- = link_to _("Help"), help_path
- - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
- %li.divider
- = render 'shared/user_dropdown_contributing_link'
- if current_user_menu?(:sign_out)
+ %li.divider
%li
= link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 39604611440..596fc3985b3 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -53,6 +53,12 @@
= sprite_icon('todo-done', size: 16)
%span.badge.badge-pill.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count)
+ %li.nav-item.header-help.dropdown
+ = link_to help_path, class: 'header-help-dropdown-toggle', data: { toggle: "dropdown" } do
+ = sprite_icon('question', size: 16)
+ = sprite_icon('angle-down', css_class: 'caret-down')
+ .dropdown-menu.dropdown-menu-right
+ = render 'layouts/header/help_dropdown'
- if header_link?(:user_dropdown)
%li.nav-item.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
new file mode 100644
index 00000000000..953c0e7f46c
--- /dev/null
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -0,0 +1,6 @@
+%ul
+ - if current_user_menu?(:help)
+ %li
+ = link_to _("Help"), help_path
+ - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
+ = render 'shared/user_dropdown_contributing_link'
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 4aa22138498..163556f4509 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -12,7 +12,7 @@
= @group.name
%ul.sidebar-top-level-items.qa-group-sidebar
- if group_sidebar_link?(:overview)
- = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups', 'analytics#show'], html_options: { class: 'home' }) do
+ = nav_link(path: group_overview_nav_link_paths, html_options: { class: 'home' }) do
= link_to group_path(@group) do
.nav-icon-container
= sprite_icon('home')
@@ -36,6 +36,16 @@
%span
= _('Activity')
+ = render_if_exists 'groups/sidebar/security_dashboard'
+
+ - if group_sidebar_link?(:contribution_analytics)
+ = nav_link(path: 'analytics#show') do
+ = link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
+ %span
+ Contribution Analytics
+
+ = render_if_exists "layouts/nav/ee/epic_link", group: @group
+
- if group_sidebar_link?(:issues)
= nav_link(path: issues_sub_menu_items) do
= link_to issues_group_path(@group) do
@@ -132,4 +142,6 @@
%span
= _('CI / CD')
+ = render_if_exists "groups/ee/settings_nav"
+
= render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/terms.html.haml b/app/views/layouts/terms.html.haml
index 977eb350365..cdad617f006 100644
--- a/app/views/layouts/terms.html.haml
+++ b/app/views/layouts/terms.html.haml
@@ -16,19 +16,18 @@
.content{ id: "content-body" }
.card
.card-header
- .card-title
- = brand_header_logo
- - logo_text = brand_header_logo_type
- - if logo_text.present?
- %span.logo-text.prepend-left-8
- = logo_text
- - if header_link?(:user_dropdown)
- .navbar-collapse
- %ul.nav.navbar-nav
- %li.header-user.dropdown
- = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
- = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
- = sprite_icon('angle-down', css_class: 'caret-down')
- .dropdown-menu.dropdown-menu-right
- = render 'layouts/header/current_user_dropdown'
+ = brand_header_logo
+ - logo_text = brand_header_logo_type
+ - if logo_text.present?
+ %span.logo-text.prepend-left-8
+ = logo_text
+ - if header_link?(:user_dropdown)
+ .navbar-collapse
+ %ul.nav.navbar-nav
+ %li.header-user.dropdown
+ = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
+ = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
+ = sprite_icon('angle-down', css_class: 'caret-down')
+ .dropdown-menu.dropdown-menu-right
+ = render 'layouts/header/current_user_dropdown'
= yield
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index f398d97028b..0f709c65d0e 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -11,10 +11,10 @@
.md-header
%ul.nav.nav-tabs.nav-links.clearfix
%li.md-header-tab.active
- %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
+ %button.js-md-write-button{ tabindex: -1 }
Write
%li.md-header-tab
- %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
+ %button.js-md-preview-button{ tabindex: -1 }
Preview
%li.md-header-toolbar.active
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index 5adca007f7e..45e1d32980c 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -5,14 +5,14 @@
= render_wiki_content(@wiki_home, legacy_render_context(params))
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
- .project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] }
- .text-center{ class: container_class }
+ .landing{ class: [('row-content-block row p-0 align-items-center' if can_create_wiki), ('content-block' unless can_create_wiki)] }
+ .col-12.col-md-3.p-0
+ .svg-content
+ = image_tag 'illustrations/wiki_login_empty.svg'
+ .col-12.col-md-9.text-center.text-md-left.pl-md-0.pl-sm-3.mb-4
%h4
- This project does not have a wiki homepage yet
+ = _("This project does not have a wiki homepage yet")
- if can_create_wiki
%p
- Add a homepage to your wiki that contains information about your project
- %p
- We recommend you
- = link_to "add a homepage", project_wiki_path(@project, :home)
- to your project's wiki and GitLab will show it here instead of this message.
+ = _("Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message.")
+ = link_to _("Create your first page"), project_wiki_path(@project, :home) + '?view=create', class: "btn btn-primary"
diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml
index 2c8dd45670f..bd46f5a4349 100644
--- a/app/views/projects/blob/_template_selectors.html.haml
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -5,13 +5,13 @@
.template-type-selector.js-template-type-selector-wrap.hidden
= dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector qa-template-type-dropdown', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector qa-license-dropdown', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
+ = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector qa-license-dropdown', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select(@project), project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector qa-gitignore-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector qa-gitignore-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names(@project) } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector qa-gitlab-ci-yml-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
+ = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector qa-gitlab-ci-yml-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls(@project) } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector qa-dockerfile-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
+ = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector qa-dockerfile-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names(@project) } } )
.template-selectors-undo-menu.hidden
%span.text-info Template applied
%button.btn.btn-sm.btn-info Undo
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index fdab8a53b41..3f2d96b70e5 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -24,7 +24,7 @@
= link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id, legacy_render: params[:legacy_render]) do
= editing_preview_title(@blob.name)
- = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
+ = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit_sha', @last_commit_sha
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 39442564a2b..4be87b9e074 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -7,7 +7,7 @@
New file
= render 'template_selectors'
.file-editor
- = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
+ = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths(@project)) do
= render 'projects/blob/editor', ref: @ref
= render 'shared/new_commit_form', placeholder: "Add new file"
diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml
index 398f76d379a..0e4b119bb54 100644
--- a/app/views/projects/branches/_panel.html.haml
+++ b/app/views/projects/branches/_panel.html.haml
@@ -9,8 +9,7 @@
.card.prepend-top-10
.card-header
- %h4.card-title
- = panel_title
+ = panel_title
%ul.content-list.all-branches
- branches.first(overview_max_branches).each do |branch|
= render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch)
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 95828626bd9..f5685d3b50d 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -48,7 +48,7 @@
- if job.try(:allow_failure)
%span.badge.badge-danger allowed to fail
- if job.schedulable?
- %span.badge.badge-info= s_('DelayedJobs|scheduled')
+ %span.badge.badge-info= s_('DelayedJobs|delayed')
- elsif job.action?
%span.badge.badge-info manual
@@ -108,7 +108,7 @@
.btn.btn-default.has-tooltip{ disabled: true,
title: job.scheduled_at }
= sprite_icon('planning')
- = duration_in_numbers(job.execute_in, true)
+ = duration_in_numbers(job.execute_in)
- confirmation_message = s_("DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes.") % { job_name: job.name }
= link_to play_project_job_path(job.project, job, return_to: request.original_url),
method: :post,
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 28998acdc13..4917f4b8903 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -10,4 +10,4 @@
noteable_data: serialize_issuable(@issue),
noteable_type: 'Issue',
target_type: 'issue',
- current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } }
+ current_user_data: UserSerializer.new.represent(current_user, {only_path: true}, CurrentUserEntity).to_json } }
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index a678cb6f058..5374f4a1de0 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -8,12 +8,13 @@
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
- refs_path = refs_namespace_project_path(@project.namespace, @project, search: '')
- .create-mr-dropdown-wrap{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
+ .create-mr-dropdown-wrap.d-inline-block{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
.btn-group.unavailable
%button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
= icon('spinner', class: 'fa-spin')
%span.text
Checking branch availability…
+
.btn-group.available.hidden
%button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } }
= value
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index c39fd0063be..b50b3ca207b 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -77,11 +77,12 @@
#related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }
// This element is filled in using JavaScript.
- .content-block.emoji-block
+ .content-block.emoji-block.emoji-block-sticky
.row
- .col-sm-8.js-noteable-awards
+ .col-md-12.col-lg-6.js-noteable-awards
= render 'award_emoji/awards_block', awardable: @issue, inline: true
- .col-sm-4.new-branch-col
+ .col-md-12.col-lg-6.new-branch-col
+ #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
= render 'new_branch' unless @issue.confidential?
%section.issuable-discussion
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index ef2fa8668c0..efc2d88172e 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -51,8 +51,10 @@
= tab_link_for @merge_request, :diffs do
Changes
%span.badge.badge-pill= @merge_request.diff_size
-
- #js-vue-discussion-counter
+ .d-inline-flex.flex-wrap
+ #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@merge_request),
+ notes_filters: UserPreference.notes_filters.to_json } }
+ #js-vue-discussion-counter
.tab-content#diff-notes-app
#notes.notes.tab-pane.voting_notes
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index d99b809c387..eede8704564 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,5 +1,4 @@
-- @breadcrumb_link = dashboard_projects_path
-- breadcrumb_title "Projects"
+- @hide_breadcrumbs = true
- @hide_top_links = true
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index dbb563f51ea..2575efc0981 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -13,7 +13,11 @@
= pluralize @pipeline.total_size, "job"
- if @pipeline.ref
from
- = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
+ - if @pipeline.ref_exists?
+ = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
+ - else
+ %span.ref-name
+ = @pipeline.ref
- if @pipeline.duration
in
= time_interval_in_words(@pipeline.duration)
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
index 9a06eca89bb..1913d06a6f8 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -1,8 +1,7 @@
.protected-branches-list.js-protected-branches-list.qa-protected-branches-list
- if @protected_branches.empty?
.card-header.bg-white
- %h3.card-title.mb-0
- Protected branch (#{@protected_branches_count})
+ Protected branch (#{@protected_branches_count})
%p.settings-message.text-center
There are currently no protected branches, protect a branch with the form above.
- else
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index c3b8f2f8964..d617d85afc2 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -2,8 +2,7 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' }
.card
.card-header
- %h3.card-title
- Protect a branch
+ Protect a branch
.card-body
= form_errors(@protected_branch)
.form-group.row
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index b274c73d035..cbf1938664c 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -2,8 +2,7 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-tags-settings' }
.card
.card-header
- %h3.card-title
- Protect a tag
+ Protect a tag
.card-body
= form_errors(@protected_tag)
.form-group.row
diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml
index c3081d75fb4..382ea848243 100644
--- a/app/views/projects/protected_tags/shared/_tags_list.html.haml
+++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml
@@ -1,8 +1,7 @@
.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.card-header
- %h3.card-title
- Protected tag (#{@protected_tags_count})
+ Protected tag (#{@protected_tags_count})
%p.settings-message.text-center
There are currently no protected tags, protect a tag with the form above.
- else
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 0426f2215ad..db1f15f96b8 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -18,8 +18,7 @@
.col-lg-12
.card
.card-header
- %h4.card-title
- = s_('ContainerRegistry|How to use the Container Registry')
+ = s_('ContainerRegistry|How to use the Container Registry')
.card-body
%p
- link_token = link_to(_('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank')
diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml
index 98d64fafe86..597c029f755 100644
--- a/app/views/projects/services/prometheus/_metrics.html.haml
+++ b/app/views/projects/services/prometheus/_metrics.html.haml
@@ -2,9 +2,8 @@
.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } }
.card-header
- %h3.card-title
- = s_('PrometheusService|Common metrics')
- %span.badge.badge-pill.js-monitored-count 0
+ = s_('PrometheusService|Common metrics')
+ %span.badge.badge-pill.js-monitored-count 0
.card-body
.loading-metrics.js-loading-metrics
%p.prepend-top-10.prepend-left-10
@@ -17,10 +16,9 @@
.card.hidden.js-panel-missing-env-vars
.card-header
- %h3.card-title
- = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
- = s_('PrometheusService|Missing environment variable')
- %span.badge.badge-pill.js-env-var-count 0
+ = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
+ = s_('PrometheusService|Missing environment variable')
+ %span.badge.badge-pill.js-env-var-count 0
.card-body.hidden
.flash-container
.flash-notice
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 24724394259..5e6d06d980e 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -20,8 +20,9 @@
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
- = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
+ = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select monospace', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
+ = icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide'
.form-text.text-muted
= s_('TagsPage|Existing branch name, tag, or commit SHA')
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 587aeafa82f..5e0523f0b96 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -1,6 +1,6 @@
.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path }
.table-holder
- %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
+ %table.table#tree-slider{ class: "table_#{@hex_path} tree-table qa-file-tree" }
%thead
%tr
%th= s_('ProjectFileTree|Name')
diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml
index a15bb4c4f3f..a559ce41e57 100644
--- a/app/views/projects/triggers/_index.html.haml
+++ b/app/views/projects/triggers/_index.html.haml
@@ -3,8 +3,7 @@
= render "projects/triggers/content"
.card
.card-header
- %h4.card-title
- Manage your project's triggers
+ Manage your project's triggers
.card-body
= render "projects/triggers/form", btn_text: "Add trigger"
%hr
diff --git a/app/views/shared/_user_dropdown_contributing_link.html.haml b/app/views/shared/_user_dropdown_contributing_link.html.haml
index 333d6fa3489..564d21a39be 100644
--- a/app/views/shared/_user_dropdown_contributing_link.html.haml
+++ b/app/views/shared/_user_dropdown_contributing_link.html.haml
@@ -1,5 +1,3 @@
%li
= link_to "https://about.gitlab.com/contributing", target: '_blank', class: 'text-nowrap' do
= _("Contribute to GitLab")
- = sprite_icon('external-link', size: 16)
-%li.divider
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index c4d177361e7..cb45928d9a5 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -36,7 +36,7 @@
%button.btn.btn-link{ type: 'button' }
= sprite_icon('search')
%span
- Press Enter or click to search
+ = _('Press Enter or click to search')
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link{ type: 'button' }
@@ -61,7 +61,7 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link{ type: 'button' }
- No Assignee
+ = _('No Assignee')
%li.divider.droplab-item-ignore
- if current_user
= render 'shared/issuable/user_dropdown_item',
@@ -74,13 +74,16 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link{ type: 'button' }
- No Milestone
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
%li.filter-dropdown-item{ data: { value: 'upcoming' } }
%button.btn.btn-link{ type: 'button' }
- Upcoming
+ = _('Upcoming')
%li.filter-dropdown-item{ 'data-value' => 'started' }
%button.btn.btn-link{ type: 'button' }
- Started
+ = _('Started')
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
@@ -90,7 +93,7 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link{ type: 'button' }
- No Label
+ = _('No Label')
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml
index 583b33a8a1b..660ee6d5777 100644
--- a/app/views/shared/issuable/_sidebar_todo.html.haml
+++ b/app/views/shared/issuable/_sidebar_todo.html.haml
@@ -1,6 +1,6 @@
- is_collapsed = local_assigns.fetch(:is_collapsed, false)
-- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : _('Mark todo as done')
-- todo_content = is_collapsed ? icon('plus-square') : _('Add todo')
+- mark_content = is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : _('Mark todo as done')
+- todo_content = is_collapsed ? sprite_icon('todo-add') : _('Add todo')
%button.issuable-todo-btn.js-issuable-todo{ type: 'button',
class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn float-right'),
diff --git a/app/views/shared/runners/show.html.haml b/app/views/shared/runners/show.html.haml
index 362569bfbaf..f62eed694d2 100644
--- a/app/views/shared/runners/show.html.haml
+++ b/app/views/shared/runners/show.html.haml
@@ -24,7 +24,7 @@
%td= @runner.active? ? 'Yes' : 'No'
%tr
%td Protected
- %td= @runner.active? ? _('Yes') : _('No')
+ %td= @runner.ref_protected? ? 'Yes' : 'No'
%tr
%td Can run untagged jobs
%td= @runner.run_untagged? ? 'Yes' : 'No'
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index c8a5e199674..6bc748d346e 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1,8 +1,9 @@
- @hide_top_links = true
-- add_to_breadcrumbs "Snippets", dashboard_snippets_path
-- breadcrumb_title "New"
+- @hide_breadcrumbs = true
- page_title "New Snippet"
-%h3.page-title
- New Snippet
-%hr
-= render "shared/snippets/form", url: snippets_path(@snippet)
+
+.page-title-holder
+ %h1.page-title= _('New Snippet')
+
+.prepend-top-default
+ = render "shared/snippets/form", url: snippets_path(@snippet)
diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb
index d9df42c9e17..2965f3b1150 100644
--- a/app/workers/namespaceless_project_destroy_worker.rb
+++ b/app/workers/namespaceless_project_destroy_worker.rb
@@ -32,7 +32,5 @@ class NamespacelessProjectDestroyWorker
merge_requests = project.forked_from_project.merge_requests.opened.from_project(project)
merge_requests.update_all(state: 'closed')
-
- project.forked_project_link.destroy
end
end
diff --git a/bin/profile-url b/bin/profile-url
index d8d09641624..9e8585aabba 100755
--- a/bin/profile-url
+++ b/bin/profile-url
@@ -48,7 +48,7 @@ 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]),
+ user: UserFinder.new(options[:username]).find_by_username,
private_token: ENV['PRIVATE_TOKEN'])
printer = RubyProf::CallStackPrinter.new(result)
diff --git a/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml b/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml
deleted file mode 100644
index 27c70318b92..00000000000
--- a/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix link handling for issue cards to avoid too sensitive drag events.
-merge_request: 21910
-author: Johann Hubert Sonntagbauer
-type: fixed
diff --git a/changelogs/unreleased/-50928-stale-list-of-issues-on-board-after-click-back.yml b/changelogs/unreleased/-50928-stale-list-of-issues-on-board-after-click-back.yml
deleted file mode 100644
index e71b09ec2e3..00000000000
--- a/changelogs/unreleased/-50928-stale-list-of-issues-on-board-after-click-back.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix stale issue boards after browser back
-merge_request: 22006
-author: Johann Hubert Sonntagbauer
-type: fixed
diff --git a/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml b/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml
deleted file mode 100644
index 074cc9d642b..00000000000
--- a/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show percentage of language detection on the language bar
-merge_request: 22056
-author: Johann Hubert Sonntagbauer
-type: added
diff --git a/changelogs/unreleased/1801-allow-event_filter-to-be-set-in-the-url.yml b/changelogs/unreleased/1801-allow-event_filter-to-be-set-in-the-url.yml
deleted file mode 100644
index 4ceaa7e3139..00000000000
--- a/changelogs/unreleased/1801-allow-event_filter-to-be-set-in-the-url.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Allow events filter to be set in the URL in addition to cookie"
-merge_request: 21557
-author: Igor @igas
-type: added
diff --git a/changelogs/unreleased/21307-send-deployment-information-in-job-api.yml b/changelogs/unreleased/21307-send-deployment-information-in-job-api.yml
deleted file mode 100644
index 8f9e24428be..00000000000
--- a/changelogs/unreleased/21307-send-deployment-information-in-job-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Send deployment information in job API
-merge_request: 21307
-author:
-type: other
diff --git a/changelogs/unreleased/21617-initialize-projects-with-readme.yml b/changelogs/unreleased/21617-initialize-projects-with-readme.yml
deleted file mode 100644
index 168f6af60c5..00000000000
--- a/changelogs/unreleased/21617-initialize-projects-with-readme.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds a initialize_with_readme parameter to POST /projects
-merge_request: 21617
-author: Steve
-type: added
diff --git a/changelogs/unreleased/21970-fix-bamboo-results.yml b/changelogs/unreleased/21970-fix-bamboo-results.yml
new file mode 100644
index 00000000000..2fbb354c477
--- /dev/null
+++ b/changelogs/unreleased/21970-fix-bamboo-results.yml
@@ -0,0 +1,5 @@
+---
+title: "Correctly process Bamboo API result array"
+merge_request: 21970
+author: Alex Lossent
+type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/22104-fix-environment-name-overlap.yml b/changelogs/unreleased/22104-fix-environment-name-overlap.yml
deleted file mode 100644
index aaa1a1709c8..00000000000
--- a/changelogs/unreleased/22104-fix-environment-name-overlap.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Fix the issue where long environment names aren't being truncated, causing the environment name to overlap into the column next to it."
-merge_request: 22104
-type: fixed
diff --git a/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml b/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml
deleted file mode 100644
index e24c55e3bad..00000000000
--- a/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add autocomplete drop down filter for project snippets
-merge_request: 21458
-author: Fabian Schneider
-type: added
diff --git a/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml b/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml
deleted file mode 100644
index a5ffc197a0c..00000000000
--- a/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Set a header for custom error pages to prevent them from being intercepted
- by gitlab-workhorse
-merge_request: 21870
-author: David Piegza
-type: fixed
diff --git a/changelogs/unreleased/23986-choose-commit-email.yml b/changelogs/unreleased/23986-choose-commit-email.yml
deleted file mode 100644
index 1ebd62cd5b1..00000000000
--- a/changelogs/unreleased/23986-choose-commit-email.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow user to choose the email used for commits made through GitLab's UI.
-merge_request: 21213
-author: Joshua Campbell
-type: added
diff --git a/changelogs/unreleased/24128-fix-comment-unresolve-discussions.yml b/changelogs/unreleased/24128-fix-comment-unresolve-discussions.yml
deleted file mode 100644
index d835d25f39b..00000000000
--- a/changelogs/unreleased/24128-fix-comment-unresolve-discussions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix resolved discussions being unresolved when commented on
-merge_request: 21881
-author:
-type: fixed
diff --git a/changelogs/unreleased/26723-discussion-filters.yml b/changelogs/unreleased/26723-discussion-filters.yml
new file mode 100644
index 00000000000..3abe95bf30d
--- /dev/null
+++ b/changelogs/unreleased/26723-discussion-filters.yml
@@ -0,0 +1,5 @@
+---
+title: Filter notes by comments or activity for issues and merge requests
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml b/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml
deleted file mode 100644
index 973dcd0496a..00000000000
--- a/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support Kubernetes RBAC for GitLab Managed Apps when creating new clusters
-merge_request: 21401
-author:
-type: changed
diff --git a/changelogs/unreleased/31887-remove-images-from-todos.yml b/changelogs/unreleased/31887-remove-images-from-todos.yml
deleted file mode 100644
index 36388f66514..00000000000
--- a/changelogs/unreleased/31887-remove-images-from-todos.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Images are no longer displayed in Todo descriptions
-merge_request: 21704
-author:
-type: fixed
diff --git a/changelogs/unreleased/32959-update-todo-icon.yml b/changelogs/unreleased/32959-update-todo-icon.yml
new file mode 100644
index 00000000000..f08fd6aa89f
--- /dev/null
+++ b/changelogs/unreleased/32959-update-todo-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Update Todo icons in collapsed sidebar for Issues and MRs
+merge_request: 22534
+author:
+type: changed
diff --git a/changelogs/unreleased/35476-lazy-image-intersectionobserver.yml b/changelogs/unreleased/35476-lazy-image-intersectionobserver.yml
deleted file mode 100644
index c2c760c0ee0..00000000000
--- a/changelogs/unreleased/35476-lazy-image-intersectionobserver.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Improve lazy image loading performance by using IntersectionObserver where
- available
-merge_request: 21565
-author:
-type: performance
diff --git a/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml b/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml
deleted file mode 100644
index 04662a7cfe2..00000000000
--- a/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Adds support for Gitaly ListLastCommitsForTree RPC in order to make bulk-fetch
- of commits more performant
-merge_request: 21921
-author:
-type: performance
diff --git a/changelogs/unreleased/38304-username-API-call-case-sensitive.yml b/changelogs/unreleased/38304-username-API-call-case-sensitive.yml
new file mode 100644
index 00000000000..b89778b6c23
--- /dev/null
+++ b/changelogs/unreleased/38304-username-API-call-case-sensitive.yml
@@ -0,0 +1,5 @@
+---
+title: "Use case insensitve username lookups"
+merge_request: 21728
+author: William George
+type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml b/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml
deleted file mode 100644
index 8131e2ff54f..00000000000
--- a/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rephrase 2FA and TOTP documentation and view
-merge_request: 21998
-author: Marc Schwede
-type: other
diff --git a/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml b/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml
deleted file mode 100644
index 1ebad500e9f..00000000000
--- a/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Instance Configuration page now displays correct SSH fingerprints
-merge_request: 22081
-author:
-type: fixed
diff --git a/changelogs/unreleased/41040-long-webhook-url-problem.yml b/changelogs/unreleased/41040-long-webhook-url-problem.yml
deleted file mode 100644
index 4057e1ff325..00000000000
--- a/changelogs/unreleased/41040-long-webhook-url-problem.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix long webhook URL overflow for custom integration.
-merge_request:
-author: Kukovskii Vladimir
-type: fixed
diff --git a/changelogs/unreleased/41205-fix-filtering-issues.yml b/changelogs/unreleased/41205-fix-filtering-issues.yml
deleted file mode 100644
index ef1a11aad08..00000000000
--- a/changelogs/unreleased/41205-fix-filtering-issues.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Filter issues without an Assignee via the API
-merge_request: 22009
-author: Eva Kadlecová
-type: fixed
diff --git a/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml b/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml
deleted file mode 100644
index 582d7824d27..00000000000
--- a/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Simplify runner registration token resetting
-merge_request: 21658
-author:
-type: changed
diff --git a/changelogs/unreleased/42611-removed-branch-link.yml b/changelogs/unreleased/42611-removed-branch-link.yml
new file mode 100644
index 00000000000..03a206871b4
--- /dev/null
+++ b/changelogs/unreleased/42611-removed-branch-link.yml
@@ -0,0 +1,5 @@
+---
+title: Only render link to branch when branch still exists in pipeline page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml b/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml
deleted file mode 100644
index 171779817c8..00000000000
--- a/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move including external files in .gitlab-ci.yml from Starter to Libre
-merge_request: 21603
-author:
-type: changed
diff --git a/changelogs/unreleased/43422-Update-images-in-group-docs.yml b/changelogs/unreleased/43422-Update-images-in-group-docs.yml
new file mode 100644
index 00000000000..4c4146589ad
--- /dev/null
+++ b/changelogs/unreleased/43422-Update-images-in-group-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Update images in group docs
+merge_request: 22031
+author: Marc Schwede
+type: other
diff --git a/changelogs/unreleased/43832-adds-chdmod-to-commits-actions-api.yml b/changelogs/unreleased/43832-adds-chdmod-to-commits-actions-api.yml
deleted file mode 100644
index cea1436ae8b..00000000000
--- a/changelogs/unreleased/43832-adds-chdmod-to-commits-actions-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allows to chmod file with commits API
-merge_request: 21866
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/44596-double-title-merge-request-message.yml b/changelogs/unreleased/44596-double-title-merge-request-message.yml
deleted file mode 100644
index 714d16977fb..00000000000
--- a/changelogs/unreleased/44596-double-title-merge-request-message.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix double title in merge request chat messages.
-merge_request: 21670
-author: Kukovskii Vladimir
-type: fixed
diff --git a/changelogs/unreleased/44627-add-link-md-editor.yml b/changelogs/unreleased/44627-add-link-md-editor.yml
deleted file mode 100644
index 65551ce9c14..00000000000
--- a/changelogs/unreleased/44627-add-link-md-editor.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add link button to markdown editor toolbar
-merge_request: 18579
-author: Jan Beckmann
-type: added
diff --git a/changelogs/unreleased/44768-lazy-load-xterm-css.yml b/changelogs/unreleased/44768-lazy-load-xterm-css.yml
deleted file mode 100644
index 85f7b1984e0..00000000000
--- a/changelogs/unreleased/44768-lazy-load-xterm-css.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Lazy load xterm custom colors css
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/44998-split-admin-settings-into-multiple-sub-pages.yml b/changelogs/unreleased/44998-split-admin-settings-into-multiple-sub-pages.yml
deleted file mode 100644
index 4b398e9419d..00000000000
--- a/changelogs/unreleased/44998-split-admin-settings-into-multiple-sub-pages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Split admin settings into multiple sub pages
-merge_request: 21467
-author:
-type: other
diff --git a/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml b/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml
deleted file mode 100644
index a7f24742588..00000000000
--- a/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds Web IDE commits to usage ping
-merge_request: 22007
-author:
-type: added
diff --git a/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml b/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml
deleted file mode 100644
index 64776abdc07..00000000000
--- a/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes modal button alignment
-merge_request: 22024
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/45754-issue-mr-and-archived-projects.yml b/changelogs/unreleased/45754-issue-mr-and-archived-projects.yml
deleted file mode 100644
index d81f47d9654..00000000000
--- a/changelogs/unreleased/45754-issue-mr-and-archived-projects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Issue and MR count now ignores archived projects
-merge_request: 21721
-author:
-type: fixed
diff --git a/changelogs/unreleased/45754-open-issues-from-archived-project-listed-in-group-issue-board.yml b/changelogs/unreleased/45754-open-issues-from-archived-project-listed-in-group-issue-board.yml
deleted file mode 100644
index 34394396020..00000000000
--- a/changelogs/unreleased/45754-open-issues-from-archived-project-listed-in-group-issue-board.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: No longer show open issues from archived projects in group issue board
-merge_request: 21721
-author:
-type: fixed
diff --git a/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml b/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml
deleted file mode 100644
index dd230d5f35e..00000000000
--- a/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add GitLab version components to CI environment variables
-merge_request: 21853
-author:
-type: added
diff --git a/changelogs/unreleased/46733-move-filter-dropdown-from-font-awesome-to-our-own-icons.yml b/changelogs/unreleased/46733-move-filter-dropdown-from-font-awesome-to-our-own-icons.yml
deleted file mode 100644
index 07549781330..00000000000
--- a/changelogs/unreleased/46733-move-filter-dropdown-from-font-awesome-to-our-own-icons.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Updated icons used in filtered search dropdowns
-merge_request: 21694
-author:
-type: changed
diff --git a/changelogs/unreleased/46884-remove-card-title.yml b/changelogs/unreleased/46884-remove-card-title.yml
new file mode 100644
index 00000000000..95f08a67638
--- /dev/null
+++ b/changelogs/unreleased/46884-remove-card-title.yml
@@ -0,0 +1,5 @@
+---
+title: Remove .card-title from .card-header for BS4 migration
+merge_request: 19335
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/47398-user-is-unable-revoke-a-authorized-application-unless-user-oauth-applications-is-checked-in-admin-settings.yml b/changelogs/unreleased/47398-user-is-unable-revoke-a-authorized-application-unless-user-oauth-applications-is-checked-in-admin-settings.yml
deleted file mode 100644
index e0dc26301d4..00000000000
--- a/changelogs/unreleased/47398-user-is-unable-revoke-a-authorized-application-unless-user-oauth-applications-is-checked-in-admin-settings.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Allow user to revoke an authorized application even if User OAuth applications
- setting is disabled in admin settings
-merge_request: 21835
-author:
-type: changed
diff --git a/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml b/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml
deleted file mode 100644
index f70011ac827..00000000000
--- a/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Reduce queries needed to compute notification recipients
-merge_request: 22050
-author:
-type: performance
diff --git a/changelogs/unreleased/48004-db-initialize-migrate.yml b/changelogs/unreleased/48004-db-initialize-migrate.yml
deleted file mode 100644
index 0d691babeca..00000000000
--- a/changelogs/unreleased/48004-db-initialize-migrate.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support db migration and initialization for Auto DevOps
-merge_request: 21955
-author:
-type: added
diff --git a/changelogs/unreleased/48222-fix-todos-status-button.yml b/changelogs/unreleased/48222-fix-todos-status-button.yml
deleted file mode 100644
index 2f7c79a07d0..00000000000
--- a/changelogs/unreleased/48222-fix-todos-status-button.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix the state of the Done button when there is an error in the GitLab Todos
- section
-merge_request:
-author: marcos8896
-type: fixed
diff --git a/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml b/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml
deleted file mode 100644
index 042731fb9be..00000000000
--- a/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Skip creating auto devops jobs for sast, container_scanning, dast, dependency_scanning
- when not licensed
-merge_request: 21959
-author:
-type: performance
diff --git a/changelogs/unreleased/48731-show-empty-state-on-wiki-only-projects.yml b/changelogs/unreleased/48731-show-empty-state-on-wiki-only-projects.yml
new file mode 100644
index 00000000000..8fd1b48b00e
--- /dev/null
+++ b/changelogs/unreleased/48731-show-empty-state-on-wiki-only-projects.yml
@@ -0,0 +1,6 @@
+---
+title: Update the empty state on wiki-only projects to display an empty state that
+ is more consistent with the rest of the system.
+merge_request: 22262
+author:
+type: changed
diff --git a/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml b/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml
deleted file mode 100644
index 75018d57e5b..00000000000
--- a/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix vertical alignment of text in diffs
-merge_request: 21573
-author:
-type: fixed
diff --git a/changelogs/unreleased/4907-improve-build-create-performance-for-license-management.yml b/changelogs/unreleased/4907-improve-build-create-performance-for-license-management.yml
deleted file mode 100644
index e82bda6819f..00000000000
--- a/changelogs/unreleased/4907-improve-build-create-performance-for-license-management.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Dont create license_management build when not included in license
-merge_request: 21958
-author:
-type: performance
diff --git a/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml b/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml
deleted file mode 100644
index 2c65c92dd8b..00000000000
--- a/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set user status from within user menu
-merge_request: 21643
-author:
-type: added
diff --git a/changelogs/unreleased/49329-mr-show-commit-details.yml b/changelogs/unreleased/49329-mr-show-commit-details.yml
deleted file mode 100644
index 23cfc0c675e..00000000000
--- a/changelogs/unreleased/49329-mr-show-commit-details.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show commit details for selected commit in MR diffs
-merge_request: 21784
-author:
-type: fixed
diff --git a/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml b/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml
deleted file mode 100644
index 5e2be42c8b7..00000000000
--- a/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds new 'Overview' tab on user profile page
-merge_request: 21663
-author:
-type: other
diff --git a/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml b/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml
deleted file mode 100644
index aa19b816b0b..00000000000
--- a/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix filter bar height bug when a tag is added
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/49990-enable-omniauth-by-default.yml b/changelogs/unreleased/49990-enable-omniauth-by-default.yml
deleted file mode 100644
index 0c08bdf6ece..00000000000
--- a/changelogs/unreleased/49990-enable-omniauth-by-default.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable omniauth by default
-merge_request: 21700
-author:
-type: changed
diff --git a/changelogs/unreleased/50111-improve-design-of-cluster-apps-to-handle-larger-quantity.yml b/changelogs/unreleased/50111-improve-design-of-cluster-apps-to-handle-larger-quantity.yml
deleted file mode 100644
index 438c847327a..00000000000
--- a/changelogs/unreleased/50111-improve-design-of-cluster-apps-to-handle-larger-quantity.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve install flow of Kubernetes cluster apps
-merge_request: 21567
-author:
-type: changed
diff --git a/changelogs/unreleased/50161-hide-close-mr-button-when-merged.yml b/changelogs/unreleased/50161-hide-close-mr-button-when-merged.yml
deleted file mode 100644
index e6dd78a7a19..00000000000
--- a/changelogs/unreleased/50161-hide-close-mr-button-when-merged.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hides Close Merge request btn on merged Merge request
-merge_request: 21840
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml b/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml
deleted file mode 100644
index cc7a79d25e5..00000000000
--- a/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix sorting by priority or popularity on group issues page, when also searching
- issue content
-merge_request: 21521
-author:
-type: fixed
diff --git a/changelogs/unreleased/50289-vendor-ci-yml-for-the-last-time.yml b/changelogs/unreleased/50289-vendor-ci-yml-for-the-last-time.yml
deleted file mode 100644
index 7aee8720888..00000000000
--- a/changelogs/unreleased/50289-vendor-ci-yml-for-the-last-time.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update all gitlab CI templates from gitlab-org/gitlab-ci-yml
-merge_request: 21929
-author:
-type: added
diff --git a/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml b/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml
deleted file mode 100644
index 09ec4b8d73d..00000000000
--- a/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix timeout when running the RemoveRestrictedTodos background migration
-merge_request: 21893
-author:
-type: fixed
diff --git a/changelogs/unreleased/50461-add-retried-builds-in-pipeline-stage-endpoint.yml b/changelogs/unreleased/50461-add-retried-builds-in-pipeline-stage-endpoint.yml
deleted file mode 100644
index 539aea4f333..00000000000
--- a/changelogs/unreleased/50461-add-retried-builds-in-pipeline-stage-endpoint.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add retried jobs to pipeline stage
-merge_request: 21558
-author:
-type: other
diff --git a/changelogs/unreleased/50552-unable-to-close-performance-bar.yml b/changelogs/unreleased/50552-unable-to-close-performance-bar.yml
deleted file mode 100644
index e3619149d2a..00000000000
--- a/changelogs/unreleased/50552-unable-to-close-performance-bar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix performance bar modal position
-merge_request: 21577
-author:
-type: fixed
diff --git a/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml b/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml
deleted file mode 100644
index 88a2ab802c8..00000000000
--- a/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes 500 for cherry pick API with empty branch name
-merge_request: 21501
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/50678-ignores-project-pending-delete.yml b/changelogs/unreleased/50678-ignores-project-pending-delete.yml
deleted file mode 100644
index e4594abba99..00000000000
--- a/changelogs/unreleased/50678-ignores-project-pending-delete.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Excludes project marked from deletion to projects API
-merge_request: 21542
-author: Jacopo Beschi @jacopo-beschi
-type: changed
diff --git a/changelogs/unreleased/50728-re-arrange-help-related-user-menu-items-into-new-help-menu.yml b/changelogs/unreleased/50728-re-arrange-help-related-user-menu-items-into-new-help-menu.yml
new file mode 100644
index 00000000000..dfda65ef91b
--- /dev/null
+++ b/changelogs/unreleased/50728-re-arrange-help-related-user-menu-items-into-new-help-menu.yml
@@ -0,0 +1,5 @@
+---
+title: Re-arrange help-related user menu items into new Help menu
+merge_request: 22195
+author:
+type: added
diff --git a/changelogs/unreleased/50808-choosing-initialize-repo-with-a-readme-breaks-project-created-from-template.yml b/changelogs/unreleased/50808-choosing-initialize-repo-with-a-readme-breaks-project-created-from-template.yml
deleted file mode 100644
index f9ed5683e63..00000000000
--- a/changelogs/unreleased/50808-choosing-initialize-repo-with-a-readme-breaks-project-created-from-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'create from template: hide checkbox for initializing repository with readme'
-merge_request: 21646
-author:
-type: other
diff --git a/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml b/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml
deleted file mode 100644
index 24e231ed88a..00000000000
--- a/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add sorting for labels on labels page
-merge_request: 21642
-author:
-type: added
diff --git a/changelogs/unreleased/50904-move-job-page-vue.yml b/changelogs/unreleased/50904-move-job-page-vue.yml
deleted file mode 100644
index e907c6301ec..00000000000
--- a/changelogs/unreleased/50904-move-job-page-vue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use Vue components and new API to render Artifacts, Trigger Variables and Commit blocks on Job page
-merge_request: 21777
-author:
-type: other
diff --git a/changelogs/unreleased/50904-update-scroll-utils.yml b/changelogs/unreleased/50904-update-scroll-utils.yml
deleted file mode 100644
index e301de1a40b..00000000000
--- a/changelogs/unreleased/50904-update-scroll-utils.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extracts scroll position check into reusable functions
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/50904-use-vuex-store-job.yml b/changelogs/unreleased/50904-use-vuex-store-job.yml
deleted file mode 100644
index 5b1112a4f5b..00000000000
--- a/changelogs/unreleased/50904-use-vuex-store-job.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Uses Vuex store in job details page and removes old mediator pattern
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/50904-vuex-show-block.yml b/changelogs/unreleased/50904-vuex-show-block.yml
deleted file mode 100644
index 5607ba3216f..00000000000
--- a/changelogs/unreleased/50904-vuex-show-block.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Renders Job show page in new Vue app
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml b/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml
deleted file mode 100644
index 5c8c78b8de8..00000000000
--- a/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add trigger information in job API
-merge_request: 21495
-author:
-type: other
diff --git a/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml b/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml
deleted file mode 100644
index 99946b954ce..00000000000
--- a/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove 'rbac_clusters' feature flag
-merge_request: 22096
-author:
-type: changed
diff --git a/changelogs/unreleased/51021-more-attr-encrypted.yml b/changelogs/unreleased/51021-more-attr-encrypted.yml
deleted file mode 100644
index 0e18c59f1bb..00000000000
--- a/changelogs/unreleased/51021-more-attr-encrypted.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Encrypt webhook tokens and URLs in the database
-merge_request: 21645
-author:
-type: security
diff --git a/changelogs/unreleased/51050-fix.yml b/changelogs/unreleased/51050-fix.yml
deleted file mode 100644
index b58f9ae34f8..00000000000
--- a/changelogs/unreleased/51050-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broken styling when issue board is collapsed
-merge_request: 21868
-author: Andrea Leone
-type: fixed
diff --git a/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml b/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml
deleted file mode 100644
index fdc75e28824..00000000000
--- a/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add empty state illustration information in job API
-merge_request: 21532
-author:
-type: other
diff --git a/changelogs/unreleased/51273-expose-runners-for-build-in-job-api.yml b/changelogs/unreleased/51273-expose-runners-for-build-in-job-api.yml
deleted file mode 100644
index df43f1dfbae..00000000000
--- a/changelogs/unreleased/51273-expose-runners-for-build-in-job-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose project runners in job API
-merge_request: 21618
-author:
-type: other
diff --git a/changelogs/unreleased/51282-link-to-user-snippets-on-new-user-snippet-page.yml b/changelogs/unreleased/51282-link-to-user-snippets-on-new-user-snippet-page.yml
deleted file mode 100644
index 1d2075ae549..00000000000
--- a/changelogs/unreleased/51282-link-to-user-snippets-on-new-user-snippet-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add link to User Snippets in breadcrumbs of New User Snippet page
-merge_request:
-author: J.D. Bean
-type: add
diff --git a/changelogs/unreleased/51386-broken-border-reports.yml b/changelogs/unreleased/51386-broken-border-reports.yml
new file mode 100644
index 00000000000..720b4edc467
--- /dev/null
+++ b/changelogs/unreleased/51386-broken-border-reports.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes broken borders for reports section in MR widget
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml b/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml
deleted file mode 100644
index da02a91041b..00000000000
--- a/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Docs for Project/Groups members API with inherited members
-merge_request: 21984
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/51450-vendor-refactor-registry-login.yml b/changelogs/unreleased/51450-vendor-refactor-registry-login.yml
deleted file mode 100644
index 417f12b4955..00000000000
--- a/changelogs/unreleased/51450-vendor-refactor-registry-login.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Vendor Auto-DevOps.gitlab-ci.yml to refactor registry_login
-merge_request: 21714
-author: Laurent Goderre @LaurentGoderre
-type: changed
diff --git a/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml b/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml
deleted file mode 100644
index d4e4503508d..00000000000
--- a/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevents private profile help link from toggling checkbox
-merge_request: 21757
-author:
-type: other
diff --git a/changelogs/unreleased/51509-remove-sidekiq-limit-fetch.yml b/changelogs/unreleased/51509-remove-sidekiq-limit-fetch.yml
deleted file mode 100644
index fcc5aae2bda..00000000000
--- a/changelogs/unreleased/51509-remove-sidekiq-limit-fetch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove background job throttling feature
-merge_request: 21748
-author:
-type: removed
diff --git a/changelogs/unreleased/51564-fix-commit-email-usage.yml b/changelogs/unreleased/51564-fix-commit-email-usage.yml
deleted file mode 100644
index 2f1b042ae8a..00000000000
--- a/changelogs/unreleased/51564-fix-commit-email-usage.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Respect the user commit email in more places
-merge_request: 21773
-author:
-type: fixed
diff --git a/changelogs/unreleased/51569-performance-bar.yml b/changelogs/unreleased/51569-performance-bar.yml
deleted file mode 100644
index ab62e7d3b3e..00000000000
--- a/changelogs/unreleased/51569-performance-bar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes performance bar looking for a key in a undefined prop
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/51571-wrapper-rake-task-uploads-migrate-os.yml b/changelogs/unreleased/51571-wrapper-rake-task-uploads-migrate-os.yml
deleted file mode 100644
index 50710ca0aa8..00000000000
--- a/changelogs/unreleased/51571-wrapper-rake-task-uploads-migrate-os.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add wrapper rake task to migrate all uploads to OS
-merge_request: 21779
-author:
-type: other
diff --git a/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml b/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml
deleted file mode 100644
index 6720430e5fc..00000000000
--- a/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Retroactively fill pipeline source for external pipelines.
-merge_request: 21814
-author:
-type: other
diff --git a/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml b/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml
new file mode 100644
index 00000000000..ad43c512ba3
--- /dev/null
+++ b/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce new model to persist specific cluster information
+merge_request: 22404
+author:
+type: added
diff --git a/changelogs/unreleased/51725-push-mirrors-default-branch-reset-to-master.yml b/changelogs/unreleased/51725-push-mirrors-default-branch-reset-to-master.yml
deleted file mode 100644
index b3caa119253..00000000000
--- a/changelogs/unreleased/51725-push-mirrors-default-branch-reset-to-master.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Doesn't synchronize the default branch for push mirrors
-merge_request: 21861
-author:
-type: fixed
diff --git a/changelogs/unreleased/51748-filter-any-milestone-via-api.yml b/changelogs/unreleased/51748-filter-any-milestone-via-api.yml
deleted file mode 100644
index 30304e5a4ac..00000000000
--- a/changelogs/unreleased/51748-filter-any-milestone-via-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allows to filter issues by Any milestone in the API
-merge_request: 22080
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml b/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml
deleted file mode 100644
index e67cc27f852..00000000000
--- a/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Includes commit stats in POST project commits API
-merge_request: 21968
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/51839-remove-sorting-on-project-tags.yml b/changelogs/unreleased/51839-remove-sorting-on-project-tags.yml
deleted file mode 100644
index 38a7c06b34c..00000000000
--- a/changelogs/unreleased/51839-remove-sorting-on-project-tags.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Preserve order of project tags list
-merge_request: 21897
-author:
-type: changed
diff --git a/changelogs/unreleased/51925-expose-has_trace-in-job-api.yml b/changelogs/unreleased/51925-expose-has_trace-in-job-api.yml
deleted file mode 100644
index eade86d97ac..00000000000
--- a/changelogs/unreleased/51925-expose-has_trace-in-job-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose has_trace in job API
-merge_request: 21950
-author:
-type: other
diff --git a/changelogs/unreleased/51942-auto-devops-local-tiller.yml b/changelogs/unreleased/51942-auto-devops-local-tiller.yml
deleted file mode 100644
index 0088d6dc598..00000000000
--- a/changelogs/unreleased/51942-auto-devops-local-tiller.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use local tiller for Auto DevOps
-merge_request: 22036
-author:
-type: changed
diff --git a/changelogs/unreleased/51955-change-single-item-breadcrumbs-to-page-titles.yml b/changelogs/unreleased/51955-change-single-item-breadcrumbs-to-page-titles.yml
new file mode 100644
index 00000000000..63fa84d2d51
--- /dev/null
+++ b/changelogs/unreleased/51955-change-single-item-breadcrumbs-to-page-titles.yml
@@ -0,0 +1,5 @@
+---
+title: Change single-item breadcrumbs to page titles
+merge_request: 22155
+author:
+type: changed
diff --git a/changelogs/unreleased/52035-selecting-an-autofill-suggestion-for-project-name-will-not-update-the-project-slug.yml b/changelogs/unreleased/52035-selecting-an-autofill-suggestion-for-project-name-will-not-update-the-project-slug.yml
deleted file mode 100644
index d56c814b139..00000000000
--- a/changelogs/unreleased/52035-selecting-an-autofill-suggestion-for-project-name-will-not-update-the-project-slug.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update project path on project name autofill
-merge_request: 22016
-author:
-type: other
diff --git a/changelogs/unreleased/52059-filter-milestone-by-none-any.yml b/changelogs/unreleased/52059-filter-milestone-by-none-any.yml
new file mode 100644
index 00000000000..5511440c0b9
--- /dev/null
+++ b/changelogs/unreleased/52059-filter-milestone-by-none-any.yml
@@ -0,0 +1,5 @@
+---
+title: Added `Any` option to milestones filter
+merge_request: 22351
+author: Heinrich Lee Yu
+type: added
diff --git a/changelogs/unreleased/52178-markdown-table-borders.yml b/changelogs/unreleased/52178-markdown-table-borders.yml
deleted file mode 100644
index 965f21f2a97..00000000000
--- a/changelogs/unreleased/52178-markdown-table-borders.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add borders and white background to markdown tables
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml b/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml
deleted file mode 100644
index d96c2bc7acd..00000000000
--- a/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Trim whitespace when inviting a new user by email
-merge_request: 22119
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml b/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml
deleted file mode 100644
index 3fea6f33451..00000000000
--- a/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix incorrect parent path on group settings page
-merge_request: 22142
-author:
-type: fixed
diff --git a/changelogs/unreleased/52299-follow-up-from-resolve-add-status-message-from-within-user-menu.yml b/changelogs/unreleased/52299-follow-up-from-resolve-add-status-message-from-within-user-menu.yml
new file mode 100644
index 00000000000..8ea0693037f
--- /dev/null
+++ b/changelogs/unreleased/52299-follow-up-from-resolve-add-status-message-from-within-user-menu.yml
@@ -0,0 +1,5 @@
+---
+title: Fix size of emojis of user status in user menu
+merge_request: 22194
+author:
+type: fixed
diff --git a/changelogs/unreleased/52361-fix-file-tree-mobile.yml b/changelogs/unreleased/52361-fix-file-tree-mobile.yml
deleted file mode 100644
index fe978eeca2d..00000000000
--- a/changelogs/unreleased/52361-fix-file-tree-mobile.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve MR file tree in smaller screens
-merge_request: 22273
-author:
-type: fixed
diff --git a/changelogs/unreleased/52472-pipeline-endpoint-json.yml b/changelogs/unreleased/52472-pipeline-endpoint-json.yml
deleted file mode 100644
index feff195beb8..00000000000
--- a/changelogs/unreleased/52472-pipeline-endpoint-json.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix caching issue with pipelines URL
-merge_request: 22293
-author:
-type: fixed
diff --git a/changelogs/unreleased/52519-runners-link.yml b/changelogs/unreleased/52519-runners-link.yml
deleted file mode 100644
index 5d904a8b340..00000000000
--- a/changelogs/unreleased/52519-runners-link.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes stuck block URL linking to documentation instead of settings page
-merge_request: 22286
-author:
-type: fixed
diff --git a/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml b/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml
deleted file mode 100644
index 9abad3d2cd8..00000000000
--- a/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow Issue and Merge Request sidebar to be toggled from collapsed state
-merge_request: 22353
-author:
-type: fixed
diff --git a/changelogs/unreleased/52559-applications-api-get-delete.yml b/changelogs/unreleased/52559-applications-api-get-delete.yml
new file mode 100644
index 00000000000..19f98a2bb56
--- /dev/null
+++ b/changelogs/unreleased/52559-applications-api-get-delete.yml
@@ -0,0 +1,5 @@
+---
+title: Add Applications API endpoints for listing and deleting entries.
+merge_request: 22296
+author: Jean-Baptiste Vasseur
+type: added
diff --git a/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml b/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml
deleted file mode 100644
index bddc1e16fab..00000000000
--- a/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide pagination for personal projects on profile overview tab
-merge_request: 22321
-author:
-type: other
diff --git a/changelogs/unreleased/52570-erased-block.yml b/changelogs/unreleased/52570-erased-block.yml
deleted file mode 100644
index 6ec295bf81b..00000000000
--- a/changelogs/unreleased/52570-erased-block.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix erased block not being rendered when job was erased
-merge_request: 22294
-author:
-type: fixed
diff --git a/changelogs/unreleased/52608-sidebar.yml b/changelogs/unreleased/52608-sidebar.yml
deleted file mode 100644
index 9eca30f7b95..00000000000
--- a/changelogs/unreleased/52608-sidebar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hides sidebar for job page in mobile
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/52614-update-job-started-check.yml b/changelogs/unreleased/52614-update-job-started-check.yml
deleted file mode 100644
index 60ea237dbf3..00000000000
--- a/changelogs/unreleased/52614-update-job-started-check.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes triggered/created labeled in job header
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml b/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml
deleted file mode 100644
index fdbde709e77..00000000000
--- a/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Load correct stage in the stages dropdown
-merge_request: 22317
-author:
-type: fixed
diff --git a/changelogs/unreleased/52669-fixes-quick-actions-preview.yml b/changelogs/unreleased/52669-fixes-quick-actions-preview.yml
deleted file mode 100644
index 51b1425d04d..00000000000
--- a/changelogs/unreleased/52669-fixes-quick-actions-preview.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes close/reopen quick actions preview for issues and merge_requests
-merge_request: 22343
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/52692-catch-redirect-loops.yml b/changelogs/unreleased/52692-catch-redirect-loops.yml
new file mode 100644
index 00000000000..655124b8fb4
--- /dev/null
+++ b/changelogs/unreleased/52692-catch-redirect-loops.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 error when testing webhooks with redirect loops
+merge_request: 22447
+author: Heinrich Lee Yu
+type: fixed
diff --git a/changelogs/unreleased/52772-assign-me-quick-action-doesn-t-work-if-there-is-extra-white-space.yml b/changelogs/unreleased/52772-assign-me-quick-action-doesn-t-work-if-there-is-extra-white-space.yml
new file mode 100644
index 00000000000..4451cdd78b5
--- /dev/null
+++ b/changelogs/unreleased/52772-assign-me-quick-action-doesn-t-work-if-there-is-extra-white-space.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve assign-me quick action doesn't work if there is extra white space
+merge_request: 22402
+author:
+type: fixed
diff --git a/changelogs/unreleased/52840-fix-runners-details-page.yml b/changelogs/unreleased/52840-fix-runners-details-page.yml
new file mode 100644
index 00000000000..b061390fcf0
--- /dev/null
+++ b/changelogs/unreleased/52840-fix-runners-details-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix rendering of 'Protected' value on Runner details page
+merge_request: 22459
+author:
+type: fixed
diff --git a/changelogs/unreleased/52886-fix-broken-master.yml b/changelogs/unreleased/52886-fix-broken-master.yml
new file mode 100644
index 00000000000..c6488c83e3b
--- /dev/null
+++ b/changelogs/unreleased/52886-fix-broken-master.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes broken test in master
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/53013-duplicate-escape.yml b/changelogs/unreleased/53013-duplicate-escape.yml
new file mode 100644
index 00000000000..c5ec2322fb5
--- /dev/null
+++ b/changelogs/unreleased/53013-duplicate-escape.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicate escape in job sidebar
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/53023-endless-scroll-loader-is-visible-on-user-profile-overview-page.yml b/changelogs/unreleased/53023-endless-scroll-loader-is-visible-on-user-profile-overview-page.yml
new file mode 100644
index 00000000000..0377e10fe9e
--- /dev/null
+++ b/changelogs/unreleased/53023-endless-scroll-loader-is-visible-on-user-profile-overview-page.yml
@@ -0,0 +1,4 @@
+title: Adds container to pager to enable scoping
+merge_request: 22529
+? author
+type: other
diff --git a/changelogs/unreleased/5987-project-templates-api.yml b/changelogs/unreleased/5987-project-templates-api.yml
deleted file mode 100644
index a627ba9f0de..00000000000
--- a/changelogs/unreleased/5987-project-templates-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow file templates to be requested at the project level
-merge_request: 7776
-author:
-type: added
diff --git a/changelogs/unreleased/6717_extend_reports_for_security_products.yml b/changelogs/unreleased/6717_extend_reports_for_security_products.yml
deleted file mode 100644
index 184c3212e54..00000000000
--- a/changelogs/unreleased/6717_extend_reports_for_security_products.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extend reports feature to support Security Products
-merge_request: 21892
-author:
-type: added
diff --git a/changelogs/unreleased/_acet-fix-diff-file-header.yml b/changelogs/unreleased/_acet-fix-diff-file-header.yml
deleted file mode 100644
index 2fb3293072d..00000000000
--- a/changelogs/unreleased/_acet-fix-diff-file-header.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix showing diff file header for renamed files
-merge_request: 22089
-author:
-type: fixed
diff --git a/changelogs/unreleased/_acet-fix-placeholder-note.yml b/changelogs/unreleased/_acet-fix-placeholder-note.yml
deleted file mode 100644
index 68f3d0085c9..00000000000
--- a/changelogs/unreleased/_acet-fix-placeholder-note.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix rendering placeholder notes
-merge_request: 22078
-author:
-type: fixed
diff --git a/changelogs/unreleased/add-2fa-button.yml b/changelogs/unreleased/add-2fa-button.yml
deleted file mode 100644
index 6cb71d52781..00000000000
--- a/changelogs/unreleased/add-2fa-button.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add button to download 2FA codes
-merge_request:
-author: Luke Picciau
-type: added
diff --git a/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml b/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml
deleted file mode 100644
index 69432c0d20c..00000000000
--- a/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add markdown header toolbar button to insert table
-merge_request: 18480
-author: George Tsiolis
-type: added
diff --git a/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml b/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml
deleted file mode 100644
index 7c707cfe5a0..00000000000
--- a/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add copy to clipboard button for application id and secret
-merge_request: 21978
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/add-gl-link-to-download-viewer.yml b/changelogs/unreleased/add-gl-link-to-download-viewer.yml
deleted file mode 100644
index ce3d916f045..00000000000
--- a/changelogs/unreleased/add-gl-link-to-download-viewer.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add link component to DownloadViewer component
-merge_request: 21987
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/add-gl-link-to-markdown-header.yml b/changelogs/unreleased/add-gl-link-to-markdown-header.yml
new file mode 100644
index 00000000000..b8fe66ab52a
--- /dev/null
+++ b/changelogs/unreleased/add-gl-link-to-markdown-header.yml
@@ -0,0 +1,5 @@
+---
+title: Change markdown header tab anchor links to buttons
+merge_request: 21988
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml b/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml
deleted file mode 100644
index ef87ef541dd..00000000000
--- a/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add link component to UserAvatarLink component
-merge_request: 21986
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/add-installation-type-backup-information.yml b/changelogs/unreleased/add-installation-type-backup-information.yml
deleted file mode 100644
index 24cf4cc21f4..00000000000
--- a/changelogs/unreleased/add-installation-type-backup-information.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add installation type to backup information file
-merge_request: 22150
-author:
-type: changed
diff --git a/changelogs/unreleased/add-most-stars-for-filter-option.yml b/changelogs/unreleased/add-most-stars-for-filter-option.yml
deleted file mode 100644
index be95d6db55f..00000000000
--- a/changelogs/unreleased/add-most-stars-for-filter-option.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allows to sort projects by most stars
-merge_request: 21762
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml b/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml
new file mode 100644
index 00000000000..87023ede020
--- /dev/null
+++ b/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce new kubernetes helpers
+merge_request: 22525
+author:
+type: other
diff --git a/changelogs/unreleased/add-role-binding-to-kubeclient.yml b/changelogs/unreleased/add-role-binding-to-kubeclient.yml
new file mode 100644
index 00000000000..bc343116eb4
--- /dev/null
+++ b/changelogs/unreleased/add-role-binding-to-kubeclient.yml
@@ -0,0 +1,5 @@
+---
+title: Allow kubeclient to call RoleBinding methods
+merge_request: 22524
+author:
+type: other
diff --git a/changelogs/unreleased/add_reliable_fetcher.yml b/changelogs/unreleased/add_reliable_fetcher.yml
deleted file mode 100644
index c08c755e546..00000000000
--- a/changelogs/unreleased/add_reliable_fetcher.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use Reliable Sidekiq fetch
-merge_request: 21715
-author:
-type: fixed
diff --git a/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml b/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml
deleted file mode 100644
index db68ed10a7e..00000000000
--- a/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Align collapsed sidebar avatar container
-merge_request: 22044
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/align-form-labels.yml b/changelogs/unreleased/align-form-labels.yml
deleted file mode 100644
index fd781e3b910..00000000000
--- a/changelogs/unreleased/align-form-labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Align form labels following Bootstrap 4 docs
-merge_request: 21752
-author:
-type: fixed
diff --git a/changelogs/unreleased/auth.yml b/changelogs/unreleased/auth.yml
deleted file mode 100644
index cd4bbf0059e..00000000000
--- a/changelogs/unreleased/auth.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add access control to GitLab pages and make it possible to enable/disable it in project settings
-merge_request: 18589
-author: Tuomo Ala-Vannesluoma
-type: added
diff --git a/changelogs/unreleased/autodevops-timed-incremental-rollout.yml b/changelogs/unreleased/autodevops-timed-incremental-rollout.yml
deleted file mode 100644
index 72c7b41177d..00000000000
--- a/changelogs/unreleased/autodevops-timed-incremental-rollout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add timed incremental rollout to Auto DevOps
-merge_request: 22023
-author:
-type: added
diff --git a/changelogs/unreleased/blackst0ne-bump-mermaid.yml b/changelogs/unreleased/blackst0ne-bump-mermaid.yml
new file mode 100644
index 00000000000..cb924ac8448
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-bump-mermaid.yml
@@ -0,0 +1,5 @@
+---
+title: Bump mermaid to 8.0.0-rc.8
+merge_request: 22509
+author: "@blackst0ne"
+type: changed
diff --git a/changelogs/unreleased/bvl-remove-sha-from-help.yml b/changelogs/unreleased/bvl-remove-sha-from-help.yml
deleted file mode 100644
index 37f797f98f4..00000000000
--- a/changelogs/unreleased/bvl-remove-sha-from-help.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Link to the tag for a version on the help page instead of to the commit
-merge_request: 22015
-author:
-type: changed
diff --git a/changelogs/unreleased/bvl-show-pre-release-sha.yml b/changelogs/unreleased/bvl-show-pre-release-sha.yml
deleted file mode 100644
index 524b3c374f7..00000000000
--- a/changelogs/unreleased/bvl-show-pre-release-sha.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show SHA for pre-release versions on the help page
-merge_request: 22026
-author:
-type: changed
diff --git a/changelogs/unreleased/ccr-43034_issues_controller_100_queries.yml b/changelogs/unreleased/ccr-43034_issues_controller_100_queries.yml
new file mode 100644
index 00000000000..d92c0e74c07
--- /dev/null
+++ b/changelogs/unreleased/ccr-43034_issues_controller_100_queries.yml
@@ -0,0 +1,5 @@
+---
+title: Add preload for routes and namespaces for issues controller.
+merge_request: 21651
+author:
+type: performance
diff --git a/changelogs/unreleased/ccr-50483_add_filter_for_group_milestones.yml b/changelogs/unreleased/ccr-50483_add_filter_for_group_milestones.yml
deleted file mode 100644
index f8fe50a2c48..00000000000
--- a/changelogs/unreleased/ccr-50483_add_filter_for_group_milestones.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Filter group milestones based on user membership.
-merge_request: 21660
-author:
-type: fixed
diff --git a/changelogs/unreleased/ccr-wip_filter.yml b/changelogs/unreleased/ccr-wip_filter.yml
deleted file mode 100644
index 07d85ec02ae..00000000000
--- a/changelogs/unreleased/ccr-wip_filter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added search functionality for Work In Progress (WIP) merge requests
-merge_request: 18119
-author: Chantal Rollison
-type: added
diff --git a/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml b/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml
new file mode 100644
index 00000000000..0f46efb693f
--- /dev/null
+++ b/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Change branch font type in tag creation
+merge_request: 22454
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/clean-gitlab-git.yml b/changelogs/unreleased/clean-gitlab-git.yml
deleted file mode 100644
index d7086b8eea0..00000000000
--- a/changelogs/unreleased/clean-gitlab-git.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove Rugged and shell code from Gitlab::Git
-merge_request: 21488
-author:
-type: other
diff --git a/changelogs/unreleased/clone-nurtch-demo-repo.yml b/changelogs/unreleased/clone-nurtch-demo-repo.yml
deleted file mode 100644
index c77138d27f0..00000000000
--- a/changelogs/unreleased/clone-nurtch-demo-repo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Copy nurtch demo notebooks at Jupyter startup
-merge_request: 21698
-author: Amit Rathi
-type: added
diff --git a/changelogs/unreleased/copy-changes-for-abuse-clarity.yml b/changelogs/unreleased/copy-changes-for-abuse-clarity.yml
deleted file mode 100644
index 00d9fec5e42..00000000000
--- a/changelogs/unreleased/copy-changes-for-abuse-clarity.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Increased retained event data by extending events pruner timeframe to 2 years
-merge_request: 22145
-author:
-type: changed
diff --git a/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml b/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml
deleted file mode 100644
index 723aa3eee8a..00000000000
--- a/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Synchronize the default branch when updating a remote mirror
-merge_request: 21653
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-create-note-return-discussion.yml b/changelogs/unreleased/dm-create-note-return-discussion.yml
deleted file mode 100644
index 49ab5c0ca14..00000000000
--- a/changelogs/unreleased/dm-create-note-return-discussion.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Increase performance when creating discussions on diff
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/dm-fix-assign-unassign-quick-actions.yml b/changelogs/unreleased/dm-fix-assign-unassign-quick-actions.yml
deleted file mode 100644
index bfc1ff7b8af..00000000000
--- a/changelogs/unreleased/dm-fix-assign-unassign-quick-actions.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Don't ignore first action when assign and unassign quick actions are used in
- the same comment
-merge_request: 21749
-author:
-type: fixed
diff --git a/changelogs/unreleased/drop-allow_overflow-option-duration_in_numbers.yml b/changelogs/unreleased/drop-allow_overflow-option-duration_in_numbers.yml
new file mode 100644
index 00000000000..4bece6459a0
--- /dev/null
+++ b/changelogs/unreleased/drop-allow_overflow-option-duration_in_numbers.yml
@@ -0,0 +1,5 @@
+---
+title: Drop `allow_overflow` option in `TimeHelper.duration_in_numbers`
+merge_request: 52284
+author:
+type: changed
diff --git a/changelogs/unreleased/dz-labels-subscribe-filter.yml b/changelogs/unreleased/dz-labels-subscribe-filter.yml
deleted file mode 100644
index 768c20c77c7..00000000000
--- a/changelogs/unreleased/dz-labels-subscribe-filter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add subscribe filter to group and project labels pages
-merge_request: 21965
-author:
-type: added
diff --git a/changelogs/unreleased/enable-force-write-auth-keys-restore.yml b/changelogs/unreleased/enable-force-write-auth-keys-restore.yml
deleted file mode 100644
index f6c83cc7950..00000000000
--- a/changelogs/unreleased/enable-force-write-auth-keys-restore.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable the ability to use the force env for rebuilding authorized_keys during a restore
-merge_request: 21896
-author:
-type: fixed
diff --git a/changelogs/unreleased/fa-handle_invalid_utf8_errors.yml b/changelogs/unreleased/fa-handle_invalid_utf8_errors.yml
deleted file mode 100644
index 9cae193d858..00000000000
--- a/changelogs/unreleased/fa-handle_invalid_utf8_errors.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Render 412 when invalid UTF-8 parameters are passed to controller
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/fe-ac-review-app-changes-33418.yml b/changelogs/unreleased/fe-ac-review-app-changes-33418.yml
new file mode 100644
index 00000000000..e4803683062
--- /dev/null
+++ b/changelogs/unreleased/fe-ac-review-app-changes-33418.yml
@@ -0,0 +1,5 @@
+---
+title: Adds filtered dropdown with changed files in review
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/feature-add-public-email-to-users-api.yml b/changelogs/unreleased/feature-add-public-email-to-users-api.yml
deleted file mode 100644
index 1f5d3fb113d..00000000000
--- a/changelogs/unreleased/feature-add-public-email-to-users-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds the user's public_email attribute to the API
-merge_request: 21909
-author: Alexis Reigel
-type: added
diff --git a/changelogs/unreleased/feature-flags-mvc.yml b/changelogs/unreleased/feature-flags-mvc.yml
deleted file mode 100644
index 6a709f7cf07..00000000000
--- a/changelogs/unreleased/feature-flags-mvc.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid close icon leaving the modal header
-merge_request: 21904
-author:
-type: changed
diff --git a/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml b/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml
new file mode 100644
index 00000000000..67eb6b78096
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml
@@ -0,0 +1,5 @@
+---
+title: Improve validation errors for external CI/CD configuration
+merge_request: 22394
+author:
+type: added
diff --git a/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml b/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml
deleted file mode 100644
index 62676cdad62..00000000000
--- a/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for pipeline only/except policy for modified paths
-merge_request: 21981
-author:
-type: added
diff --git a/changelogs/unreleased/feature-runner-state-filter-for-admin-view.yml b/changelogs/unreleased/feature-runner-state-filter-for-admin-view.yml
deleted file mode 100644
index b8112bd0813..00000000000
--- a/changelogs/unreleased/feature-runner-state-filter-for-admin-view.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a filter bar to the admin runners view and add a state filter
-merge_request: 19625
-author: Alexis Reigel
-type: added
diff --git a/changelogs/unreleased/feature-runner-type-filter-for-admin-view.yml b/changelogs/unreleased/feature-runner-type-filter-for-admin-view.yml
deleted file mode 100644
index e7812cd0944..00000000000
--- a/changelogs/unreleased/feature-runner-type-filter-for-admin-view.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a type filter to the admin runners view
-merge_request: 19649
-author: Alexis Reigel
-type: added
diff --git a/changelogs/unreleased/feature-set-public-email-through-api.yml b/changelogs/unreleased/feature-set-public-email-through-api.yml
deleted file mode 100644
index 22fae71e9d8..00000000000
--- a/changelogs/unreleased/feature-set-public-email-through-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for setting the public email through the api
-merge_request: 21938
-author: Alexis Reigel
-type: added
diff --git a/changelogs/unreleased/features-unauth-access-ssh-keys.yml b/changelogs/unreleased/features-unauth-access-ssh-keys.yml
deleted file mode 100644
index bae2bcfaabd..00000000000
--- a/changelogs/unreleased/features-unauth-access-ssh-keys.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable unauthenticated access to public SSH keys via the API
-merge_request: 20118
-author: Ronald Claveau
-type: changed
diff --git a/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml b/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml
deleted file mode 100644
index 4d85e1b9af2..00000000000
--- a/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow setting user's organization and location attributes through the API by adding them to the list of allowed parameters
-merge_request: 21938
-author: Alexis Reigel
-type: fixed
diff --git a/changelogs/unreleased/fix-base64-encoded-file-uploads.yml b/changelogs/unreleased/fix-base64-encoded-file-uploads.yml
new file mode 100644
index 00000000000..3dde2419fa1
--- /dev/null
+++ b/changelogs/unreleased/fix-base64-encoded-file-uploads.yml
@@ -0,0 +1,5 @@
+---
+title: Remove base64 encoding from files that contain plain text
+merge_request: 22425
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-chat-notification-service-for-ee.yml b/changelogs/unreleased/fix-chat-notification-service-for-ee.yml
deleted file mode 100644
index b69d08b95db..00000000000
--- a/changelogs/unreleased/fix-chat-notification-service-for-ee.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix activity titles for MRs in chat notification services
-merge_request: 21834
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-committer-typo.yml b/changelogs/unreleased/fix-committer-typo.yml
deleted file mode 100644
index 6033912b6c0..00000000000
--- a/changelogs/unreleased/fix-committer-typo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix committer typo
-merge_request: 21899
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/fix-events-finder-incomplete.yml b/changelogs/unreleased/fix-events-finder-incomplete.yml
deleted file mode 100644
index f3a4e421d33..00000000000
--- a/changelogs/unreleased/fix-events-finder-incomplete.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redact confidential events in the API
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/fix-help-text-font-color-in-merge-request-creation.yml b/changelogs/unreleased/fix-help-text-font-color-in-merge-request-creation.yml
deleted file mode 100644
index 4ac192cd056..00000000000
--- a/changelogs/unreleased/fix-help-text-font-color-in-merge-request-creation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix wrong text color of help text in merge request creation
-merge_request:
-author: Gerard Montemayor
-type: fixed
diff --git a/changelogs/unreleased/fix-leading-slash-in-redirects-plus-rubocop.yml b/changelogs/unreleased/fix-leading-slash-in-redirects-plus-rubocop.yml
deleted file mode 100644
index 38b2486a475..00000000000
--- a/changelogs/unreleased/fix-leading-slash-in-redirects-plus-rubocop.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix leading slash in redirects and add rubocop cop
-merge_request: 21828
-author: Sanad Liaquat
-type: fixed
diff --git a/changelogs/unreleased/fix-mention-in-edit-mr.yml b/changelogs/unreleased/fix-mention-in-edit-mr.yml
deleted file mode 100644
index a82b0ba9748..00000000000
--- a/changelogs/unreleased/fix-mention-in-edit-mr.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed mention autocomplete in edit merge request.
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/force-post-migration-dir-schema-load.yml b/changelogs/unreleased/force-post-migration-dir-schema-load.yml
deleted file mode 100644
index 19119515929..00000000000
--- a/changelogs/unreleased/force-post-migration-dir-schema-load.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensure the schema is loaded with post_migrations included
-merge_request: 21689
-author:
-type: changed
diff --git a/changelogs/unreleased/frozen-string-app-controller-more.yml b/changelogs/unreleased/frozen-string-app-controller-more.yml
deleted file mode 100644
index ea2c81e7afc..00000000000
--- a/changelogs/unreleased/frozen-string-app-controller-more.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable more frozen string in app/controllers/
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/frozen-string-app-controller.yml b/changelogs/unreleased/frozen-string-app-controller.yml
deleted file mode 100644
index 95fea4eae63..00000000000
--- a/changelogs/unreleased/frozen-string-app-controller.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable frozen string in app/controllers/**/*.rb
-merge_request: gfyoung
-author:
-type: performance
diff --git a/changelogs/unreleased/frozen-string-app-controllers-much-more.yml b/changelogs/unreleased/frozen-string-app-controllers-much-more.yml
deleted file mode 100644
index 6e32d5ba039..00000000000
--- a/changelogs/unreleased/frozen-string-app-controllers-much-more.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable even more frozen string in app/controllers
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/frozen-string-app-enforce.yml b/changelogs/unreleased/frozen-string-app-enforce.yml
deleted file mode 100644
index 44686557c45..00000000000
--- a/changelogs/unreleased/frozen-string-app-enforce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Check frozen string in style builds
-merge_request:
-author: gfyoung
-type: other
diff --git a/changelogs/unreleased/frozen-string-app-finders-graphql.yml b/changelogs/unreleased/frozen-string-app-finders-graphql.yml
deleted file mode 100644
index ea8c83f64d9..00000000000
--- a/changelogs/unreleased/frozen-string-app-finders-graphql.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable frozen string in app/graphql + app/finders
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/frozen-string-docs.yml b/changelogs/unreleased/frozen-string-docs.yml
deleted file mode 100644
index 2eb8a446ee8..00000000000
--- a/changelogs/unreleased/frozen-string-docs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update docs regarding frozen string
-merge_request:
-author: gfyoung
-type: other
diff --git a/changelogs/unreleased/frozen-string-enable-app-helpers.yml b/changelogs/unreleased/frozen-string-enable-app-helpers.yml
deleted file mode 100644
index 7f6805ccb5a..00000000000
--- a/changelogs/unreleased/frozen-string-enable-app-helpers.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable frozen string for app/helpers/**/*.rb
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/frozen-string-enable-vestigial.yml b/changelogs/unreleased/frozen-string-enable-lib-gitlab.yml
index 55313ff0fcc..d64278eb626 100644
--- a/changelogs/unreleased/frozen-string-enable-vestigial.yml
+++ b/changelogs/unreleased/frozen-string-enable-lib-gitlab.yml
@@ -1,5 +1,5 @@
---
-title: Enable frozen string in vestigial files
+title: Enable frozen string for lib/gitlab/*.rb
merge_request:
author: gfyoung
type: performance
diff --git a/changelogs/unreleased/gt-add-transparent-background-to-markdown-header-tabs.yml b/changelogs/unreleased/gt-add-transparent-background-to-markdown-header-tabs.yml
new file mode 100644
index 00000000000..2ba52e07324
--- /dev/null
+++ b/changelogs/unreleased/gt-add-transparent-background-to-markdown-header-tabs.yml
@@ -0,0 +1,5 @@
+---
+title: Add transparent background to markdown header tabs
+merge_request: 22565
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml b/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml
deleted file mode 100644
index c2e828eb697..00000000000
--- a/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicate button from the markdown header toolbar
-merge_request: 22192
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml b/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml
new file mode 100644
index 00000000000..d2a65d48d8d
--- /dev/null
+++ b/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml
@@ -0,0 +1,5 @@
+---
+title: Remove empty spec describe blocks
+merge_request: 22451
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml b/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml
deleted file mode 100644
index 7205c138777..00000000000
--- a/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update copy to clipboard button data for application secret
-merge_request: 22268
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/ide-fetch-templates-pages.yml b/changelogs/unreleased/ide-fetch-templates-pages.yml
deleted file mode 100644
index d4703e530f2..00000000000
--- a/changelogs/unreleased/ide-fetch-templates-pages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed file templates not fully being fetched in Web IDE
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/improve-empty-project-placeholder.yml b/changelogs/unreleased/improve-empty-project-placeholder.yml
deleted file mode 100644
index 11fe21e7710..00000000000
--- a/changelogs/unreleased/improve-empty-project-placeholder.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve empty project placeholder for non-members and members without write access
-merge_request: 21977
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/issue_50528.yml b/changelogs/unreleased/issue_50528.yml
deleted file mode 100644
index 82d33bfa255..00000000000
--- a/changelogs/unreleased/issue_50528.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Log project services errors when executing async
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/jivl-fix-bar-char-transient-spec-failure.yml b/changelogs/unreleased/jivl-fix-bar-char-transient-spec-failure.yml
new file mode 100644
index 00000000000..344997add74
--- /dev/null
+++ b/changelogs/unreleased/jivl-fix-bar-char-transient-spec-failure.yml
@@ -0,0 +1,5 @@
+---
+title: Fix transient spec error in the bar_chart component
+merge_request: 22495
+author:
+type: fixed
diff --git a/changelogs/unreleased/jivl-fix-monitoring-dashboard-resizing-navbar.yml b/changelogs/unreleased/jivl-fix-monitoring-dashboard-resizing-navbar.yml
deleted file mode 100644
index c21301bf6b3..00000000000
--- a/changelogs/unreleased/jivl-fix-monitoring-dashboard-resizing-navbar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix resizing of monitoring dashboard
-merge_request: 21730
-author:
-type: fixed
diff --git a/changelogs/unreleased/leipert-fix-mr-widget-header-margins.yml b/changelogs/unreleased/leipert-fix-mr-widget-header-margins.yml
deleted file mode 100644
index 9c23244d48b..00000000000
--- a/changelogs/unreleased/leipert-fix-mr-widget-header-margins.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix merge request header margins
-merge_request: 21878
-author:
-type: other
diff --git a/changelogs/unreleased/lfs-project-attribute-alias.yml b/changelogs/unreleased/lfs-project-attribute-alias.yml
new file mode 100644
index 00000000000..883869f651a
--- /dev/null
+++ b/changelogs/unreleased/lfs-project-attribute-alias.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve LFS not correctly showing enabled
+merge_request: 22501
+author:
+type: fixed
diff --git a/changelogs/unreleased/lib-api-frozen-string-enable.yml b/changelogs/unreleased/lib-api-frozen-string-enable.yml
deleted file mode 100644
index eb59f0bc2d1..00000000000
--- a/changelogs/unreleased/lib-api-frozen-string-enable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable frozen string in lib/api and lib/backup
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/load_project_features.yml b/changelogs/unreleased/load_project_features.yml
deleted file mode 100644
index 0cf7f0e3a74..00000000000
--- a/changelogs/unreleased/load_project_features.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Mitigate N+1 queries when parsing commit references in comments.
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/lock-unlock-quick-actions.yml b/changelogs/unreleased/lock-unlock-quick-actions.yml
deleted file mode 100644
index 9322d60ba52..00000000000
--- a/changelogs/unreleased/lock-unlock-quick-actions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add /lock and /unlock quick actions
-merge_request: 15197
-author: Mehdi Lahmam (@mehlah)
-type: added
diff --git a/changelogs/unreleased/mao-48221-issues_show_sql_count.yml b/changelogs/unreleased/mao-48221-issues_show_sql_count.yml
deleted file mode 100644
index d634d15946e..00000000000
--- a/changelogs/unreleased/mao-48221-issues_show_sql_count.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Banzai label ref finder - minimize SQL calls by sharing context more aggresively
-merge_request: 22070
-author:
-type: performance
diff --git a/changelogs/unreleased/mk-asymmetric-exists-cache.yml b/changelogs/unreleased/mk-asymmetric-exists-cache.yml
deleted file mode 100644
index b6eec7d1fc6..00000000000
--- a/changelogs/unreleased/mk-asymmetric-exists-cache.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Resolve "Geo: Does not mark repositories as missing on primary due to stale
- cache"'
-merge_request: 21789
-author:
-type: fixed
diff --git a/changelogs/unreleased/more-table-widths.yml b/changelogs/unreleased/more-table-widths.yml
deleted file mode 100644
index 61956e7068e..00000000000
--- a/changelogs/unreleased/more-table-widths.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds an extra width to the responsive tables
-merge_request: 21928
-author:
-type: other
diff --git a/changelogs/unreleased/mr-creation-source-project-filtering.yml b/changelogs/unreleased/mr-creation-source-project-filtering.yml
new file mode 100644
index 00000000000..818101a6f1b
--- /dev/null
+++ b/changelogs/unreleased/mr-creation-source-project-filtering.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed source project not filtering in merge request creation compare form
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/mr-file-list.yml b/changelogs/unreleased/mr-file-list.yml
new file mode 100644
index 00000000000..0a2a5e0c1cc
--- /dev/null
+++ b/changelogs/unreleased/mr-file-list.yml
@@ -0,0 +1,5 @@
+---
+title: Switch between tree list & file list in diffs file browser
+merge_request: 22191
+author:
+type: added
diff --git a/changelogs/unreleased/mr-file-tree-data.yml b/changelogs/unreleased/mr-file-tree-data.yml
deleted file mode 100644
index a82087ea148..00000000000
--- a/changelogs/unreleased/mr-file-tree-data.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added tree of changed files to merge request diffs
-merge_request: 21833
-author:
-type: added
diff --git a/changelogs/unreleased/mr-file-tree-inline-fluid-width-fix.yml b/changelogs/unreleased/mr-file-tree-inline-fluid-width-fix.yml
new file mode 100644
index 00000000000..b61f47724fc
--- /dev/null
+++ b/changelogs/unreleased/mr-file-tree-inline-fluid-width-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed merge request fill tree toggling not respecting fluid width preference
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/mr-legacy-diff-notes.yml b/changelogs/unreleased/mr-legacy-diff-notes.yml
deleted file mode 100644
index bca5ac8297f..00000000000
--- a/changelogs/unreleased/mr-legacy-diff-notes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correctly show legacy diff notes in the merge request changes tab
-merge_request: 21652
-author:
-type: fixed
diff --git a/changelogs/unreleased/mr-widget-discussion-state-fix.yml b/changelogs/unreleased/mr-widget-discussion-state-fix.yml
deleted file mode 100644
index 562d78a7aa7..00000000000
--- a/changelogs/unreleased/mr-widget-discussion-state-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed merge request widget discussion state not updating after resolving discussions
-merge_request: 21705
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-clean-up-phase-for-diff-files-removal.yml b/changelogs/unreleased/osw-clean-up-phase-for-diff-files-removal.yml
deleted file mode 100644
index 03189d934a7..00000000000
--- a/changelogs/unreleased/osw-clean-up-phase-for-diff-files-removal.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add clean-up phase for ScheduleDiffFilesDeletion migration
-merge_request: 21734
-author:
-type: other
diff --git a/changelogs/unreleased/osw-configurable-single-diff-file-limit.yml b/changelogs/unreleased/osw-configurable-single-diff-file-limit.yml
deleted file mode 100644
index 55a4b305885..00000000000
--- a/changelogs/unreleased/osw-configurable-single-diff-file-limit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make single diff patch limit configurable
-merge_request: 21886
-author:
-type: added
diff --git a/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml b/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml
deleted file mode 100644
index 5dde22d3158..00000000000
--- a/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix LFS uploaded images not being rendered
-merge_request: 22092
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-gitaly-diff-stats-client.yml b/changelogs/unreleased/osw-gitaly-diff-stats-client.yml
deleted file mode 100644
index 9f280162409..00000000000
--- a/changelogs/unreleased/osw-gitaly-diff-stats-client.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Gitaly diff stats RPC client
-merge_request: 21732
-author:
-type: changed
diff --git a/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml b/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml
deleted file mode 100644
index d4e2641daf5..00000000000
--- a/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes expensive dead code on main MR page request
-merge_request: 22153
-author:
-type: performance
diff --git a/changelogs/unreleased/osw-use-diff-stats-rpc-on-comparison-views.yml b/changelogs/unreleased/osw-use-diff-stats-rpc-on-comparison-views.yml
deleted file mode 100644
index c71d4e58d6f..00000000000
--- a/changelogs/unreleased/osw-use-diff-stats-rpc-on-comparison-views.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use stats RPC when comparing diffs
-merge_request: 21778
-author:
-type: fixed
diff --git a/changelogs/unreleased/pedroms-master-patch-57786.yml b/changelogs/unreleased/pedroms-master-patch-57786.yml
deleted file mode 100644
index cdf179046aa..00000000000
--- a/changelogs/unreleased/pedroms-master-patch-57786.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix blue, orange, and red color inconsistencies
-merge_request: 21972
-author:
-type: other
diff --git a/changelogs/unreleased/pipeline-event-variables.yml b/changelogs/unreleased/pipeline-event-variables.yml
deleted file mode 100644
index 90fd964efd5..00000000000
--- a/changelogs/unreleased/pipeline-event-variables.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: pipeline webhook event now contain pipeline variables
-merge_request: 18171
-author: Pierre Tardy
-type: added
diff --git a/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml b/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml
deleted file mode 100644
index 3a399bb836e..00000000000
--- a/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Rails5: fix artifacts controller download spec Rails5 has params[:file_type]
- as '''' if file_type is included as nil in the request'
-merge_request: 22123
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/rails5-fix-delete-blob.yml b/changelogs/unreleased/rails5-fix-delete-blob.yml
new file mode 100644
index 00000000000..ee8304fbdf4
--- /dev/null
+++ b/changelogs/unreleased/rails5-fix-delete-blob.yml
@@ -0,0 +1,5 @@
+---
+title: 'Rails5: fix delete blob'
+merge_request: 22456
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/rails5-fix-deployment-spec.yml b/changelogs/unreleased/rails5-fix-deployment-spec.yml
new file mode 100644
index 00000000000..9e53c617a54
--- /dev/null
+++ b/changelogs/unreleased/rails5-fix-deployment-spec.yml
@@ -0,0 +1,5 @@
+---
+title: 'Rails5: fix deployment model spec'
+merge_request: 22428
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/rails5-fix-issue-move-service.yml b/changelogs/unreleased/rails5-fix-issue-move-service.yml
deleted file mode 100644
index 1e71544e587..00000000000
--- a/changelogs/unreleased/rails5-fix-issue-move-service.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Rails 5: fix issue move service In rails 5, the attributes method for an enum
- returns the name instead of the database integer.'
-merge_request: 21616
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/remove-sidekiq.yml b/changelogs/unreleased/remove-sidekiq.yml
deleted file mode 100644
index c7bef974b89..00000000000
--- a/changelogs/unreleased/remove-sidekiq.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove sidekiq info from performance bar
-merge_request:
-author:
-type: removed
diff --git a/changelogs/unreleased/rename-local-variable.yml b/changelogs/unreleased/rename-local-variable.yml
deleted file mode 100644
index 70281dfef08..00000000000
--- a/changelogs/unreleased/rename-local-variable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rename block scope local variable in table pagination spec
-merge_request: 21969
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/rename-scheduled-label-badges.yml b/changelogs/unreleased/rename-scheduled-label-badges.yml
new file mode 100644
index 00000000000..f9ee17a98a4
--- /dev/null
+++ b/changelogs/unreleased/rename-scheduled-label-badges.yml
@@ -0,0 +1,5 @@
+---
+title: Rename "scheduled" label/badge of delayed jobs to "delayed"
+merge_request: 22245
+author:
+type: changed
diff --git a/changelogs/unreleased/rename-squash-before-merge-vue-component.yml b/changelogs/unreleased/rename-squash-before-merge-vue-component.yml
deleted file mode 100644
index 66eeeb225dd..00000000000
--- a/changelogs/unreleased/rename-squash-before-merge-vue-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rename squash before merge vue component
-merge_request: 21851
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/rouge-3-3-0.yml b/changelogs/unreleased/rouge-3-3-0.yml
deleted file mode 100644
index 7cf6c75920a..00000000000
--- a/changelogs/unreleased/rouge-3-3-0.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Update to Rouge 3.3.0 including frozen string literals for improved memory
- usage
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/scheduled-manual-jobs.yml b/changelogs/unreleased/scheduled-manual-jobs.yml
deleted file mode 100644
index fa3f5a6f461..00000000000
--- a/changelogs/unreleased/scheduled-manual-jobs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow pipelines to schedule delayed job runs
-merge_request: 21767
-author:
-type: added
diff --git a/changelogs/unreleased/security-2697-code-highlight-timeout.yml b/changelogs/unreleased/security-2697-code-highlight-timeout.yml
deleted file mode 100644
index 66ad9ff822b..00000000000
--- a/changelogs/unreleased/security-2697-code-highlight-timeout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set timeout for syntax highlighting
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-acet-issue-details.yml b/changelogs/unreleased/security-acet-issue-details.yml
deleted file mode 100644
index 64147a9d6e8..00000000000
--- a/changelogs/unreleased/security-acet-issue-details.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Sanitize JSON data properly to fix XSS on Issue details page
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml b/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml
deleted file mode 100644
index e0231b7962f..00000000000
--- a/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Markdown API no longer displays confidential title references unless authorized
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml b/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml
deleted file mode 100644
index 589d16c0c35..00000000000
--- a/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Properly filter private references from system notes
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml b/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml
deleted file mode 100644
index 7520aa624c7..00000000000
--- a/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix stored XSS in merge requests from imported repository
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-osw-user-info-leak-discussions.yml b/changelogs/unreleased/security-osw-user-info-leak-discussions.yml
deleted file mode 100644
index 5acbb80fc3d..00000000000
--- a/changelogs/unreleased/security-osw-user-info-leak-discussions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Filter user sensitive data from discussions JSON
-merge_request: 2536
-author:
-type: security
diff --git a/changelogs/unreleased/security-package-json-xss.yml b/changelogs/unreleased/security-package-json-xss.yml
deleted file mode 100644
index 6ab4854e44f..00000000000
--- a/changelogs/unreleased/security-package-json-xss.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix xss vulnerability sourced from package.json
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/sh-add-audit-logging-json-ce.yml b/changelogs/unreleased/sh-add-audit-logging-json-ce.yml
new file mode 100644
index 00000000000..3c0a27da269
--- /dev/null
+++ b/changelogs/unreleased/sh-add-audit-logging-json-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for JSON logging for audit events
+merge_request: 22471
+author:
+type: added
diff --git a/changelogs/unreleased/sh-allow-key-id-in-params.yml b/changelogs/unreleased/sh-allow-key-id-in-params.yml
deleted file mode 100644
index 2be1cfb0ed3..00000000000
--- a/changelogs/unreleased/sh-allow-key-id-in-params.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Filter any parameters ending with "key" in logs
-merge_request: 21688
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-associate-rakefile-ruby.yml b/changelogs/unreleased/sh-associate-rakefile-ruby.yml
new file mode 100644
index 00000000000..3e3fcb8d860
--- /dev/null
+++ b/changelogs/unreleased/sh-associate-rakefile-ruby.yml
@@ -0,0 +1,5 @@
+---
+title: Associate Rakefile with Ruby icon in diffs
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/sh-delete-tags-outside-transaction.yml b/changelogs/unreleased/sh-delete-tags-outside-transaction.yml
deleted file mode 100644
index 974da70251e..00000000000
--- a/changelogs/unreleased/sh-delete-tags-outside-transaction.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Delete container repository tags outside of transaction
-merge_request: 21679
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-52009.yml b/changelogs/unreleased/sh-fix-issue-52009.yml
deleted file mode 100644
index fc22a58a66a..00000000000
--- a/changelogs/unreleased/sh-fix-issue-52009.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent Error 500s with invalid relative links
-merge_request: 22001
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-multipart-upload-signed-urls.yml b/changelogs/unreleased/sh-fix-multipart-upload-signed-urls.yml
deleted file mode 100644
index 994765bc1fd..00000000000
--- a/changelogs/unreleased/sh-fix-multipart-upload-signed-urls.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix object storage uploads not working with AWS v2
-merge_request: 21731
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-guard-against-ldap-login-csrf-fail.yml b/changelogs/unreleased/sh-guard-against-ldap-login-csrf-fail.yml
deleted file mode 100644
index 7233f6f3d7b..00000000000
--- a/changelogs/unreleased/sh-guard-against-ldap-login-csrf-fail.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Guard against a login attempt with invalid CSRF token
-merge_request: 21934
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-improve-container-tags-update-username.yml b/changelogs/unreleased/sh-improve-container-tags-update-username.yml
deleted file mode 100644
index 1a21ae84314..00000000000
--- a/changelogs/unreleased/sh-improve-container-tags-update-username.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve logging when username update fails due to registry tags
-merge_request: 22038
-author:
-type: other
diff --git a/changelogs/unreleased/sh-pages-eof-error.yml b/changelogs/unreleased/sh-pages-eof-error.yml
new file mode 100644
index 00000000000..497a74c1458
--- /dev/null
+++ b/changelogs/unreleased/sh-pages-eof-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix EOF detection with CI artifacts metadata
+merge_request: 22479
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-strip-github-pat-whitespace.yml b/changelogs/unreleased/sh-strip-github-pat-whitespace.yml
new file mode 100644
index 00000000000..ea26f57e8f0
--- /dev/null
+++ b/changelogs/unreleased/sh-strip-github-pat-whitespace.yml
@@ -0,0 +1,5 @@
+---
+title: Strip whitespace around GitHub personal access tokens
+merge_request: 22432
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-support-adding-confirmed-emails.yml b/changelogs/unreleased/sh-support-adding-confirmed-emails.yml
deleted file mode 100644
index 1b64a1c62dc..00000000000
--- a/changelogs/unreleased/sh-support-adding-confirmed-emails.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add ability to skip user email confirmation with API
-merge_request: 21630
-author:
-type: added
diff --git a/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml b/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml
deleted file mode 100644
index 2a27e37c053..00000000000
--- a/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump KaTeX version to 0.9.0
-merge_request: 21625
-author:
-type: fixed
diff --git a/changelogs/unreleased/support-license-management-and-performance.yml b/changelogs/unreleased/support-license-management-and-performance.yml
new file mode 100644
index 00000000000..2e65dba5e76
--- /dev/null
+++ b/changelogs/unreleased/support-license-management-and-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Support licenses and performance
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/toon-copy-meta-data-fix.yml b/changelogs/unreleased/toon-copy-meta-data-fix.yml
deleted file mode 100644
index f2f8a1a82a4..00000000000
--- a/changelogs/unreleased/toon-copy-meta-data-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow /copy_metadata for new issues and MRs
-merge_request: 21953
-author:
-type: changed
diff --git a/changelogs/unreleased/update-operations-metrics-empty-state.yml b/changelogs/unreleased/update-operations-metrics-empty-state.yml
deleted file mode 100644
index 51f3935b769..00000000000
--- a/changelogs/unreleased/update-operations-metrics-empty-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update operations metrics empty state
-merge_request: 21974
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/update-readme-ruby-version.yml b/changelogs/unreleased/update-readme-ruby-version.yml
new file mode 100644
index 00000000000..524b8112d4f
--- /dev/null
+++ b/changelogs/unreleased/update-readme-ruby-version.yml
@@ -0,0 +1,5 @@
+---
+title: Update Ruby version in README
+merge_request: 22466
+author: J.D. Bean
+type: changed
diff --git a/changelogs/unreleased/update-runner-chart-to-0-1-34.yml b/changelogs/unreleased/update-runner-chart-to-0-1-34.yml
new file mode 100644
index 00000000000..ebd34bb86b8
--- /dev/null
+++ b/changelogs/unreleased/update-runner-chart-to-0-1-34.yml
@@ -0,0 +1,5 @@
+---
+title: Update used version of Runner Helm Chart to 0.1.34
+merge_request: 22274
+author:
+type: other
diff --git a/changelogs/unreleased/update-runner-chart-to-0-1-35.yml b/changelogs/unreleased/update-runner-chart-to-0-1-35.yml
new file mode 100644
index 00000000000..3b8029c8d96
--- /dev/null
+++ b/changelogs/unreleased/update-runner-chart-to-0-1-35.yml
@@ -0,0 +1,5 @@
+---
+title: Update used version of Runner Helm Chart to 0.1.35
+merge_request: 22541
+author:
+type: other
diff --git a/changelogs/unreleased/use-raw-file-format.yml b/changelogs/unreleased/use-raw-file-format.yml
new file mode 100644
index 00000000000..d86db51fea4
--- /dev/null
+++ b/changelogs/unreleased/use-raw-file-format.yml
@@ -0,0 +1,5 @@
+---
+title: Make all legacy security reports to use raw format
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/vendor-auto-devops-gitlab-ci-fix-503-on-deploy.yml b/changelogs/unreleased/vendor-auto-devops-gitlab-ci-fix-503-on-deploy.yml
deleted file mode 100644
index 11ebf567e9d..00000000000
--- a/changelogs/unreleased/vendor-auto-devops-gitlab-ci-fix-503-on-deploy.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Vendor Auto-DevOps.gitlab-ci.yml to fix bug where the deploy job does not wait
- for Deployment to complete
-merge_request: 21713
-author:
-type: fixed
diff --git a/changelogs/unreleased/vendor-gitlab-ci-auto-devops-yml.yml b/changelogs/unreleased/vendor-gitlab-ci-auto-devops-yml.yml
deleted file mode 100644
index 98d0e24c00a..00000000000
--- a/changelogs/unreleased/vendor-gitlab-ci-auto-devops-yml.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make AutoDevOps work behind proxy
-merge_request: 21775
-author: Sergej - @kinolaev
-type: other
diff --git a/changelogs/unreleased/winh-highlight-current-user.yml b/changelogs/unreleased/winh-highlight-current-user.yml
deleted file mode 100644
index 125a5c08c4e..00000000000
--- a/changelogs/unreleased/winh-highlight-current-user.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Highlight current user in comments
-merge_request: 21406
-author:
-type: changed
diff --git a/changelogs/unreleased/winh-page-title-margin.yml b/changelogs/unreleased/winh-page-title-margin.yml
deleted file mode 100644
index f21f07d396b..00000000000
--- a/changelogs/unreleased/winh-page-title-margin.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change vertical margin of page titles to 16px
-merge_request: 21888
-author:
-type: changed
diff --git a/changelogs/unreleased/zj-render-log-artifacts.yml b/changelogs/unreleased/zj-render-log-artifacts.yml
deleted file mode 100644
index 82f29b62300..00000000000
--- a/changelogs/unreleased/zj-render-log-artifacts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Render log artifact files in GitLab
-merge_request:
-author:
-type: added
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 749cdd0f869..a4db125f831 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -772,9 +772,6 @@ test:
default:
path: tmp/tests/repositories/
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
- broken:
- path: tmp/tests/non-existent-repositories
- gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
gitaly:
client_path: tmp/tests/gitaly
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 583f05f2fb7..9ecae9790fd 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -8,7 +8,7 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ROOT_PATH = path.resolve(__dirname, '..');
-const CACHE_PATH = path.join(ROOT_PATH, 'tmp/cache');
+const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
diff --git a/danger/database/Dangerfile b/danger/database/Dangerfile
index ad5f1c1e0f3..38ccbd94edb 100644
--- a/danger/database/Dangerfile
+++ b/danger/database/Dangerfile
@@ -39,8 +39,6 @@ def database_paths_requiring_review(files)
to_review
end
-all_files = git.added_files + git.modified_files
-
non_geo_db_schema_updated = !git.modified_files.grep(%r{\Adb/schema\.rb}).empty?
geo_db_schema_updated = !git.modified_files.grep(%r{\Aee/db/geo/schema\.rb}).empty?
@@ -55,7 +53,7 @@ if geo_migration_created && !geo_db_schema_updated
warn format(SCHEMA_NOT_UPDATED_MESSAGE, migrations: 'Geo migrations', schema: gitlab.html_link("ee/db/geo/schema.rb"))
end
-db_paths_to_review = database_paths_requiring_review(all_files)
+db_paths_to_review = database_paths_requiring_review(helper.all_changed_files)
unless db_paths_to_review.empty?
message 'This merge request adds or changes files that require a ' \
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index d65bec123a9..fe819ee250c 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -11,9 +11,7 @@ def docs_paths_requiring_review(files)
end
end
-all_files = git.added_files + git.modified_files
-
-docs_paths_to_review = docs_paths_requiring_review(all_files)
+docs_paths_to_review = docs_paths_requiring_review(helper.all_changed_files)
unless docs_paths_to_review.empty?
message 'This merge request adds or changes files that require a ' \
diff --git a/danger/eslint/Dangerfile b/danger/eslint/Dangerfile
index f78488cfd0a..4916cacfd7e 100644
--- a/danger/eslint/Dangerfile
+++ b/danger/eslint/Dangerfile
@@ -7,7 +7,7 @@ def get_eslint_files(files)
end
end
-eslint_candidates = get_eslint_files(git.added_files + git.modified_files)
+eslint_candidates = get_eslint_files(helper.all_changed_files)
return if eslint_candidates.empty?
diff --git a/danger/plugins/helper.rb b/danger/plugins/helper.rb
new file mode 100644
index 00000000000..f4eb9119266
--- /dev/null
+++ b/danger/plugins/helper.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Danger
+ # Common helper functions for our danger scripts
+ # If we find ourselves repeating code in our danger files, we might as well put them in here.
+ class Helper < Plugin
+ # Returns a list of all files that have been added, modified or renamed.
+ # `git.modified_files` might contain paths that already have been renamed,
+ # so we need to remove them from the list.
+ #
+ # Considering these changes:
+ #
+ # - A new_file.rb
+ # - D deleted_file.rb
+ # - M modified_file.rb
+ # - R renamed_file_before.rb -> renamed_file_after.rb
+ #
+ # it will return
+ # ```
+ # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
+ # ```
+ #
+ # @return [Array<String>]
+ def all_changed_files
+ Set.new
+ .merge(git.added_files.to_a)
+ .merge(git.modified_files.to_a)
+ .merge(git.renamed_files.map { |x| x[:after] })
+ .subtract(git.renamed_files.map { |x| x[:before] })
+ .to_a
+ .sort
+ end
+ end
+end
diff --git a/danger/prettier/Dangerfile b/danger/prettier/Dangerfile
index 86f9f6af475..37c4b78a213 100644
--- a/danger/prettier/Dangerfile
+++ b/danger/prettier/Dangerfile
@@ -6,7 +6,7 @@ def get_prettier_files(files)
end
end
-prettier_candidates = get_prettier_files(git.added_files + git.modified_files)
+prettier_candidates = get_prettier_files(helper.all_changed_files)
return if prettier_candidates.empty?
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 51e69879c79..089de211380 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -7,8 +7,8 @@ Sidekiq::Testing.inline! do
'https://gitlab.com/gitlab-org/gitlab-shell.git',
'https://gitlab.com/gnuwget/wget2.git',
'https://gitlab.com/Commit451/LabCoat.git',
- 'https://github.com/documentcloud/underscore.git',
- 'https://github.com/twitter/flight.git',
+ 'https://github.com/jashkenas/underscore.git',
+ 'https://github.com/flightjs/flight.git',
'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git',
'https://github.com/google/material-design-lite.git',
@@ -20,18 +20,18 @@ Sidekiq::Testing.inline! do
'https://github.com/airbnb/javascript.git',
'https://github.com/tessalt/echo-chamber-js.git',
'https://github.com/atom/atom.git',
- 'https://github.com/mattermost/platform.git',
+ 'https://github.com/mattermost/mattermost-server.git',
'https://github.com/purifycss/purifycss.git',
'https://github.com/facebook/nuclide.git',
'https://github.com/wbkd/awesome-d3.git',
'https://github.com/kilimchoi/engineering-blogs.git',
'https://github.com/gilbarbara/logos.git',
- 'https://github.com/gaearon/redux.git',
+ 'https://github.com/reduxjs/redux.git',
'https://github.com/awslabs/s2n.git',
'https://github.com/arkency/reactjs_koans.git',
'https://github.com/twbs/bootstrap.git',
'https://github.com/chjj/ttystudio.git',
- 'https://github.com/DrBoolean/mostly-adequate-guide.git',
+ 'https://github.com/MostlyAdequate/mostly-adequate-guide.git',
'https://github.com/octocat/Spoon-Knife.git',
'https://github.com/opencontainers/runc.git',
'https://github.com/googlesamples/android-topeka.git'
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index 5535c4a14e5..5af77c49913 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -1,7 +1,7 @@
require './spec/support/sidekiq'
class Gitlab::Seeder::Pipelines
- STAGES = %w[build test deploy notify]
+ STAGES = %w[build test security deploy notify]
BUILDS = [
# build stage
{ name: 'build:linux', stage: 'build', status: :success,
@@ -31,6 +31,16 @@ class Gitlab::Seeder::Pipelines
{ name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true,
queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
+ # security stage
+ { name: 'dast', stage: 'security', status: :success,
+ queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
+ { name: 'sast', stage: 'security', status: :success,
+ queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
+ { name: 'dependency_scanning', stage: 'security', status: :success,
+ queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
+ { name: 'container_scanning', stage: 'security', status: :success,
+ queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
+
# deploy stage
{ name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success,
options: { environment: { action: 'start', on_stop: 'stop staging' } },
@@ -108,6 +118,11 @@ class Gitlab::Seeder::Pipelines
setup_artifacts(build)
setup_test_reports(build)
+ if build.ref == build.project.default_branch
+ setup_security_reports_file(build)
+ else
+ setup_security_reports_legacy_archive(build)
+ end
setup_build_log(build)
build.project.environments.
@@ -143,6 +158,55 @@ class Gitlab::Seeder::Pipelines
end
end
+ def setup_security_reports_file(build)
+ return unless build.stage == "security"
+
+ # we have two sources: master and feature-branch
+ branch_name = build.ref == build.project.default_branch ?
+ 'master' : 'feature-branch'
+
+ artifacts_cache_file(security_reports_path(branch_name, build.name)) do |file|
+ build.job_artifacts.build(
+ project: build.project,
+ file_type: build.name,
+ file_format: :raw,
+ file: file)
+ end
+ end
+
+ def setup_security_reports_legacy_archive(build)
+ return unless build.stage == "security"
+
+ # we have two sources: master and feature-branch
+ branch_name = build.ref == build.project.default_branch ?
+ 'master' : 'feature-branch'
+
+ artifacts_cache_file(security_reports_archive_path(branch_name)) do |file|
+ build.job_artifacts.build(
+ project: build.project,
+ file_type: :archive,
+ file_format: :zip,
+ file: file)
+ end
+
+ # assign dummy metadata
+ artifacts_cache_file(artifacts_metadata_path) do |file|
+ build.job_artifacts.build(
+ project: build.project,
+ file_type: :metadata,
+ file_format: :gzip,
+ file: file)
+ end
+
+ build.options = {
+ artifacts: {
+ paths: [
+ Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(build.name.to_sym)
+ ]
+ }
+ }
+ end
+
def setup_build_log(build)
if %w(running success failed).include?(build.status)
build.trace.set(FFaker::Lorem.paragraphs(6).join("\n\n"))
@@ -190,6 +254,15 @@ class Gitlab::Seeder::Pipelines
Rails.root + 'spec/fixtures/junit/junit.xml.gz'
end
+ def security_reports_archive_path(branch)
+ Rails.root.join('spec', 'fixtures', 'security-reports', branch + '.zip')
+ end
+
+ def security_reports_path(branch, name)
+ file_name = Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(name.to_sym)
+ Rails.root.join('spec', 'fixtures', 'security-reports', branch, file_name)
+ end
+
def artifacts_cache_file(file_path)
file = Tempfile.new("artifacts")
file.close
diff --git a/db/migrate/20180925200829_create_user_preferences.rb b/db/migrate/20180925200829_create_user_preferences.rb
new file mode 100644
index 00000000000..755cabdabde
--- /dev/null
+++ b/db/migrate/20180925200829_create_user_preferences.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class CreateUserPreferences < ActiveRecord::Migration
+ DOWNTIME = false
+
+ class UserPreference < ActiveRecord::Base
+ self.table_name = 'user_preferences'
+
+ NOTES_FILTERS = { all_notes: 0, comments: 1 }.freeze
+ end
+
+ def change
+ create_table :user_preferences do |t|
+ t.references :user,
+ null: false,
+ index: { unique: true },
+ foreign_key: { on_delete: :cascade }
+
+ t.integer :issue_notes_filter,
+ default: UserPreference::NOTES_FILTERS[:all_notes],
+ null: false, limit: 2
+
+ t.integer :merge_request_notes_filter,
+ default: UserPreference::NOTES_FILTERS[:all_notes],
+ null: false,
+ limit: 2
+
+ t.timestamps_with_timezone null: false
+ end
+ end
+end
diff --git a/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
new file mode 100644
index 00000000000..a58c190e1d6
--- /dev/null
+++ b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class CreateClustersKubernetesNamespaces < ActiveRecord::Migration
+ DOWNTIME = false
+ INDEX_NAME = 'kubernetes_namespaces_cluster_and_namespace'
+
+ def change
+ create_table :clusters_kubernetes_namespaces, id: :bigserial do |t|
+ t.references :cluster, null: false, index: true, foreign_key: { on_delete: :cascade }
+ t.references :project, index: true, foreign_key: { on_delete: :nullify }
+ t.references :cluster_project, index: true, foreign_key: { on_delete: :nullify }
+
+ t.timestamps_with_timezone null: false
+
+ t.string :encrypted_service_account_token_iv
+ t.string :namespace, null: false
+ t.string :service_account_name
+
+ t.text :encrypted_service_account_token
+
+ t.index [:cluster_id, :namespace], name: INDEX_NAME, unique: true
+ end
+ end
+end
diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
index 08d7f499eec..678876e886c 100644
--- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -113,7 +113,9 @@ class RenameReservedProjectNames < ActiveRecord::Migration
begin
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
- project.rename_repo if rename_project_row(project, path)
+ if rename_project_row(project, path)
+ Projects::AfterRenameService.new(project).execute
+ end
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
end
@@ -123,6 +125,6 @@ class RenameReservedProjectNames < ActiveRecord::Migration
def rename_project_row(project, path)
project.respond_to?(:update_attributes) &&
project.update(path: path) &&
- project.respond_to?(:rename_repo)
+ defined?(Projects::AfterRenameService)
end
end
diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
index 43a37667250..26a67b0f814 100644
--- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
+++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
@@ -55,7 +55,9 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration
begin
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
- project.rename_repo if rename_project_row(project, path)
+ if rename_project_row(project, path)
+ Projects::AfterRenameService.new(project).execute
+ end
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
end
@@ -65,6 +67,6 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration
def rename_project_row(project, path)
project.respond_to?(:update_attributes) &&
project.update(path: path) &&
- project.respond_to?(:rename_repo)
+ defined?(Projects::AfterRenameService)
end
end
diff --git a/db/schema.rb b/db/schema.rb
index 3f3bec0ce04..ddfccbba678 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -691,6 +691,23 @@ ActiveRecord::Schema.define(version: 20181013005024) do
add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree
add_index "clusters_applications_runners", ["runner_id"], name: "index_clusters_applications_runners_on_runner_id", using: :btree
+ create_table "clusters_kubernetes_namespaces", id: :bigserial, force: :cascade do |t|
+ t.integer "cluster_id", null: false
+ t.integer "project_id"
+ t.integer "cluster_project_id"
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.text "encrypted_service_account_token"
+ t.string "encrypted_service_account_token_iv"
+ t.string "namespace", null: false
+ t.string "service_account_name"
+ end
+
+ add_index "clusters_kubernetes_namespaces", ["cluster_id", "namespace"], name: "kubernetes_namespaces_cluster_and_namespace", unique: true, using: :btree
+ add_index "clusters_kubernetes_namespaces", ["cluster_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_id", using: :btree
+ add_index "clusters_kubernetes_namespaces", ["cluster_project_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_project_id", using: :btree
+ add_index "clusters_kubernetes_namespaces", ["project_id"], name: "index_clusters_kubernetes_namespaces_on_project_id", using: :btree
+
create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
@@ -2117,6 +2134,16 @@ ActiveRecord::Schema.define(version: 20181013005024) do
add_index "user_interacted_projects", ["project_id", "user_id"], name: "index_user_interacted_projects_on_project_id_and_user_id", unique: true, using: :btree
add_index "user_interacted_projects", ["user_id"], name: "index_user_interacted_projects_on_user_id", using: :btree
+ create_table "user_preferences", force: :cascade do |t|
+ t.integer "user_id", null: false
+ t.integer "issue_notes_filter", limit: 2, default: 0, null: false
+ t.integer "merge_request_notes_filter", limit: 2, default: 0, null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ end
+
+ add_index "user_preferences", ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree
+
create_table "user_statuses", primary_key: "user_id", force: :cascade do |t|
t.integer "cached_markdown_version"
t.string "emoji", default: "speech_balloon", null: false
@@ -2325,6 +2352,9 @@ ActiveRecord::Schema.define(version: 20181013005024) do
add_foreign_key "clusters_applications_prometheus", "clusters", name: "fk_557e773639", on_delete: :cascade
add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify
add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_kubernetes_namespaces", "cluster_projects", on_delete: :nullify
+ add_foreign_key "clusters_kubernetes_namespaces", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_kubernetes_namespaces", "projects", on_delete: :nullify
add_foreign_key "container_repositories", "projects"
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
@@ -2440,6 +2470,7 @@ ActiveRecord::Schema.define(version: 20181013005024) do
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade
add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade
+ add_foreign_key "user_preferences", "users", on_delete: :cascade
add_foreign_key "user_statuses", "users", on_delete: :cascade
add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users", "application_setting_terms", column: "accepted_term_id", name: "fk_789cd90b35", on_delete: :cascade
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 2feac1fd3b0..a1ea78b64bd 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -1,12 +1,10 @@
# Jobs artifacts administration
->**Notes:**
->- Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
->- Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
- changed to `ZIP`.
->- Starting with GitLab 8.17, builds are renamed to jobs.
->- This is the administration documentation. For the user guide see
- [pipelines/job_artifacts](../user/project/pipelines/job_artifacts.md).
+> **Notes:**
+> - Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
+> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format changed to `ZIP`.
+> - Starting with GitLab 8.17, builds are renamed to jobs.
+> - This is the administration documentation. For the user guide see [pipelines/job_artifacts](../user/project/pipelines/job_artifacts.md).
Artifacts is a list of files and directories which are attached to a job
after it completes successfully. This feature is enabled by default in all
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index a8cdd20581d..0e41a38b7e2 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -144,6 +144,20 @@ December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/
error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
```
+## `audit_json.log`
+
+This file lives in `/var/log/gitlab/gitlab-rails/audit_json.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/audit_json.log` for
+installations from source.
+
+Changes to group or project settings are logged to this file. For example:
+
+```json
+{"severity":"INFO","time":"2018-10-17T17:38:22.523Z","author_id":3,"entity_id":2,"entity_type":"Project","change":"visibility","from":"Private","to":"Public","author_name":"John Doe4","target_id":2,"target_type":"Project","target_details":"namespace2/project2"}
+{"severity":"INFO","time":"2018-10-17T17:38:22.830Z","author_id":5,"entity_id":3,"entity_type":"Project","change":"name","from":"John Doe7 / project3","to":"John Doe7 / new name","author_name":"John Doe6","target_id":3,"target_type":"Project","target_details":"namespace3/project3"}
+{"severity":"INFO","time":"2018-10-17T17:38:23.175Z","author_id":7,"entity_id":4,"entity_type":"Project","change":"path","from":"","to":"namespace4/newpath","author_name":"John Doe8","target_id":4,"target_type":"Project","target_details":"namespace4/newpath"}
+```
+
## `sidekiq.log`
This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 2952a98626a..d8345f2d6bd 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -242,6 +242,33 @@ verification requirement. Navigate to `Admin area âž” Settings` and uncheck
**Require users to prove ownership of custom domains** in the Pages section.
This setting is enabled by default.
+### Access control
+
+Access control was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422)
+in GitLab 11.5. It can be configured per-project, and allows access to a Pages
+site to be controlled based on a user's membership to that project.
+
+Access control works by registering the Pages daemon as an OAuth application
+with GitLab. Whenever a request to access a private Pages site is made by an
+unauthenticated user, the Pages daemon redirects the user to GitLab. If
+authentication is successful, the user is redirected back to Pages with a token,
+which is persisted in a cookie. The cookies are signed with a secret key, so
+tampering can be detected.
+
+Each request to view a resource in a private site is authenticated by Pages
+using that token. For each request it receives, it makes a request to the GitLab
+API to check that the user is authorized to read that site.
+
+Pages access control is currently disabled by default. To enable it, you must:
+
+1. Enable it in `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ gitlab_pages['access_control'] = true
+ ```
+
+1. [Reconfigure GitLab][reconfigure]
+
## Activate verbose logging for daemon
Verbose logging was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2533) in
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index 295905a7625..ddff54be575 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -391,6 +391,44 @@ the first one with a backslash (\). For example `pages.example.io` would be:
server_name ~^.*\.pages\.example\.io$;
```
+## Access control
+
+Access control was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422)
+in GitLab 11.5. It can be configured per-project, and allows access to a Pages
+site to be controlled based on a user's membership to that project.
+
+Access control works by registering the Pages daemon as an OAuth application
+with GitLab. Whenever a request to access a private Pages site is made by an
+unauthenticated user, the Pages daemon redirects the user to GitLab. If
+authentication is successful, the user is redirected back to Pages with a token,
+which is persisted in a cookie. The cookies are signed with a secret key, so
+tampering can be detected.
+
+Each request to view a resource in a private site is authenticated by Pages
+using that token. For each request it receives, it makes a request to the GitLab
+API to check that the user is authorized to read that site.
+
+Pages access control is currently disabled by default. To enable it, you must:
+
+1. Modify your `config/gitlab.yml` file:
+ ```yaml
+ pages:
+ access_control: true
+ ```
+1. [Restart GitLab][restart]
+1. Create a new [system OAuth application](../../integration/oauth_provider.md#adding-an-application-through-the-profile)
+ This should be called `GitLab Pages` and have a `Redirect URL` of
+ `https://projects.example.io/auth`. It does not need to be a "trusted"
+ application, but it does need the "api" scope.
+1. Start the Pages daemon with the following additional arguments:
+
+ ```shell
+ -auth-client-secret <OAuth code generated by GitLab> \
+ -auth-redirect-uri http://projects.example.io/auth \
+ -auth-secret <40 random hex characters> \
+ -auth-server <URL of the GitLab instance>
+ ```
+
## Change storage path
Follow the steps below to change the default path where GitLab Pages' contents
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index 715bc0cd08c..8b725e50f58 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -31,7 +31,7 @@ panel.
If the repository check fails for some repository you should look up the error
in `repocheck.log`:
-- in the [admin panel](logs.md#repocheck.log)
+- in the [admin panel](logs.md#repocheck-log)
- or on disk, see:
- `/var/log/gitlab/gitlab-rails` for Omnibus installations
- `/home/git/gitlab/log` for installations from source
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index bd758c49eba..9379944b250 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -41,11 +41,8 @@ Registry, etc.
## Hashed Storage
-> **Warning:** Hashed storage is in **Beta**. For the latest updates, check the
-> associated [issue](https://gitlab.com/gitlab-com/infrastructure/issues/3542)
-> and please report any problems you encounter.
-Hashed Storage is the new storage behavior we are rolling out with 10.0. Instead
+Hashed Storage is the new storage behavior we rolled out with 10.0. Instead
of coupling project URL and the folder structure where the repository will be
stored on disk, we are coupling a hash, based on the project's ID. This makes
the folder structure immutable, and therefore eliminates any requirement to
diff --git a/doc/api/README.md b/doc/api/README.md
index a351db99bbd..a620a13a3b3 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -69,6 +69,7 @@ following locations:
- [System Hooks](system_hooks.md)
- [Tags](tags.md)
- [Todos](todos.md)
+- [Triggering Pipelines](../ci/triggers/README.md)
- [Users](users.md)
- [Validate CI configuration](lint.md)
- [V3 to V4](v3_to_v4.md)
@@ -233,7 +234,10 @@ provided you are authenticated as an administrator with an OAuth or Personal Acc
You need to pass the `sudo` parameter either via query string or a header with an ID/username of
the user you want to perform the operation as. If passed as a header, the
-header name must be `Sudo`.
+header name must be `Sudo`.
+
+NOTE: **Note:**
+Usernames are case insensitive.
If a non administrative access token is provided, an error message will
be returned with status code `403`:
diff --git a/doc/api/applications.md b/doc/api/applications.md
index 6d244594b71..d74a3cdf5c1 100644
--- a/doc/api/applications.md
+++ b/doc/api/applications.md
@@ -4,12 +4,12 @@
[ce-8160]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8160
+Only admin user can use the Applications API.
+
## Create a application
Create a application by posting a JSON payload.
-User must be admin to do that.
-
Returns `200` if the request succeeds.
```
@@ -30,8 +30,55 @@ Example response:
```json
{
+ "id":1,
"application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef0d218477e55f11cf12fc3737",
+ "application_name": "MyApplication",
"secret": "ee1dd64b6adc89cf7e2c23099301ccc2c61b441064e9324d963c46902a85ec34",
"callback_url": "http://redirect.uri"
}
```
+
+## List all applications
+
+List all registered applications.
+
+```
+GET /applications
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/applications
+```
+
+Example response:
+
+```json
+[
+ {
+ "id":1,
+ "application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef0d218477e55f11cf12fc3737",
+ "application_name": "MyApplication",
+ "callback_url": "http://redirect.uri"
+ }
+]
+```
+
+> Note: the `secret` value will not be exposed by this API.
+
+## Delete an application
+
+Delete a specific application.
+
+Returns `204` if the request succeeds.
+
+```
+DELETE /applications/:id
+```
+
+Parameters:
+
+- `id` (required) - The id of the application (not the application_id)
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/applications/:id
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index be75c363a40..a9462fc413f 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -37,6 +37,7 @@ GET /groups
"request_access_enabled": false,
"full_name": "Foobar Group",
"full_path": "foo-bar",
+ "file_template_project_id": 1,
"parent_id": null
}
]
@@ -62,6 +63,7 @@ GET /groups?statistics=true
"request_access_enabled": false,
"full_name": "Foobar Group",
"full_path": "foo-bar",
+ "file_template_project_id": 1,
"parent_id": null,
"statistics": {
"storage_size" : 212,
@@ -122,6 +124,7 @@ GET /groups/:id/subgroups
"request_access_enabled": false,
"full_name": "Foobar Group",
"full_path": "foo-bar",
+ "file_template_project_id": 1,
"parent_id": 123
}
]
@@ -232,6 +235,7 @@ Example response:
"request_access_enabled": false,
"full_name": "Twitter",
"full_path": "twitter",
+ "file_template_project_id": 1,
"parent_id": null,
"projects": [
{
@@ -386,6 +390,7 @@ Example response:
"request_access_enabled": false,
"full_name": "Twitter",
"full_path": "twitter",
+ "file_template_project_id": 1,
"parent_id": null
}
```
@@ -442,6 +447,7 @@ PUT /groups/:id
| `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. |
| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
| `request_access_enabled` | boolean | no | Allow users to request member access. |
+| `file_template_project_id` | integer | no | **(Premium)** The ID of a project to load custom file templates from |
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental"
@@ -462,6 +468,7 @@ Example response:
"request_access_enabled": false,
"full_name": "Foobar Group",
"full_path": "foo-bar",
+ "file_template_project_id": 1,
"parent_id": null,
"projects": [
{
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 44940bdd9e5..9f6740ad86a 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -98,7 +98,7 @@ POST /projects/:id/issues/:issue_iid/notes
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
-- `issue_id` (required) - The IID of an issue
+- `issue_iid` (required) - The IID of an issue
- `body` (required) - The content of a note
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights)
diff --git a/doc/api/project_templates.md b/doc/api/project_templates.md
index ebdfa975849..ef98205cd68 100644
--- a/doc/api/project_templates.md
+++ b/doc/api/project_templates.md
@@ -1,19 +1,23 @@
# Project templates API
-This API is a project-specific implementation of these endpoints:
+This API is a project-specific version of these endpoints:
- [Dockerfile templates](templates/dockerfiles.md)
- [Gitignore templates](templates/gitignores.md)
- [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
- [Open source license templates](templates/licenses.md)
-It deprecates those endpoints, which will be removed for API version 5.
+It deprecates these endpoints, which will be removed for API version 5.
-Project-specific templates will be added to this API in time. This includes, but
-is not limited to:
+In addition to templates common to the entire instance, project-specific
+templates are also available from this API endpoint.
-- [Issue and Merge Request templates](../user/project/description_templates.html)
-- [Group level file templates](https://gitlab.com/gitlab-org/gitlab-ee/issues/5987) **(Premium)**
+Support will be added for [Issue and Merge Request templates](../user/project/description_templates.md)
+in a future release.
+
+Support for [Group-level file templates](../user/group/index.md#group-level-file-templates-premium)
+**[PREMIUM]** was [added](https://gitlab.com/gitlab-org/gitlab-ee/issues/5987)
+in GitLab 11.5
## Get all templates of a particular type
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index f5ac3816fe5..5dbf6cb0760 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -14,7 +14,7 @@ GET /projects/:id/repository/tree
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
-- `path` (optional) - The path inside repository. Used to get contend of subdirectories
+- `path` (optional) - The path inside repository. Used to get content of subdirectories
- `ref` (optional) - The name of a repository branch or tag or if not given the default branch
- `recursive` (optional) - Boolean value used to get a recursive tree (false by default)
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 0bcbd0aebf0..071c13f41cb 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -340,8 +340,7 @@ Example response:
## List project's runners
List all runners (specific and shared) available in the project. Shared runners
-are listed if at least one shared runner is defined **and** shared runners
-usage is enabled in the project's settings.
+are listed if at least one shared runner is defined.
```
GET /projects/:id/runners
diff --git a/doc/api/users.md b/doc/api/users.md
index 07f03f9c827..ee24aa09156 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -59,6 +59,9 @@ GET /users?active=true
GET /users?blocked=true
```
+NOTE: **Note:**
+Username search is case insensitive.
+
### For admins
```
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 0b64c8caba7..3b41036cd14 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -395,8 +395,67 @@ If you're running multiple Runners you will have to modify all configuration fil
> login to GitLab's Container Registry.
Once you've built a Docker image, you can push it up to the built-in
-[GitLab Container Registry](../../user/project/container_registry.md). For example,
-if you're using docker-in-docker on your runners, this is how your `.gitlab-ci.yml`
+[GitLab Container Registry](../../user/project/container_registry.md).
+Some things you should be aware of:
+
+- You must [log in to the container registry](#authenticating-to-the-container-registry)
+ before running commands. You can do this in the `before_script` if multiple
+ jobs depend on it.
+- Using `docker build --pull` fetches any changes to base
+ images before building just in case your cache is stale. It takes slightly
+ longer, but means you don’t get stuck without security patches to base images.
+- Doing an explicit `docker pull` before each `docker run` fetches
+ the latest image that was just built. This is especially important if you are
+ using multiple runners that cache images locally. Using the git SHA in your
+ image tag makes this less necessary since each job will be unique and you
+ shouldn't ever have a stale image. However, it's still possible to have a
+ stale image if you re-build a given commit after a dependency has changed.
+- You don't want to build directly to `latest` tag in case there are multiple jobs
+ happening simultaneously.
+
+### Authenticating to the Container Registry
+
+There are three ways to authenticate to the Container Registry via GitLab CI/CD
+and depend on the visibility of your project.
+
+For all projects, mostly suitable for public ones:
+
+- **Using the special `gitlab-ci-token` user**: This user is created for you in order to
+ push to the Registry connected to your project. Its password is automatically
+ set with the `$CI_JOB_TOKEN` variable. This allows you to automate building and deploying
+ your Docker images and has read/write access to the Registry. This is ephemeral,
+ so it's only valid for one job. You can use the following example as-is:
+
+ ```sh
+ docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ ```
+
+For private and internal projects:
+
+- **Using a personal access token**: You can create and use a
+ [personal access token](../../user/profile/personal_access_tokens.md)
+ in case your project is private:
+ - For read (pull) access, the scope should be `read_registry`.
+ - For read/write (pull/push) access, use `api`.
+ Replace the `<username>` and `<access_token>` in the following example:
+
+ ```sh
+ docker login -u <username> -p <access_token> $CI_REGISTRY
+ ```
+
+- **Using the GitLab Deploy Token**: You can create and use a
+ [special deploy token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
+ with your private projects. It provides read-only (pull) access to the Registry.
+ Once created, you can use the special environment variables, and GitLab CI/CD
+ will fill them in for you. You can use the following example as-is:
+
+ ```sh
+ docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY
+ ```
+
+### Container Registry examples
+
+If you're using docker-in-docker on your Runners, this is how your `.gitlab-ci.yml`
could look like:
```yaml
@@ -414,11 +473,6 @@ could look like:
- docker push registry.example.com/group/project/image:latest
```
-You have to use the special `gitlab-ci-token` user created for you in order to
-push to the Registry connected to your project. Its password is provided in the
-`$CI_JOB_TOKEN` variable. This allows you to automate building and deployment
-of your Docker images.
-
You can also make use of [other variables](../variables/README.md) to avoid hardcoding:
```yaml
@@ -508,22 +562,6 @@ deploy:
- master
```
-Some things you should be aware of when using the Container Registry:
-
-- You must log in to the container registry before running commands. Putting
- this in `before_script` will run it before each job.
-- Using `docker build --pull` makes sure that Docker fetches any changes to base
- images before building just in case your cache is stale. It takes slightly
- longer, but means you don’t get stuck without security patches to base images.
-- Doing an explicit `docker pull` before each `docker run` makes sure to fetch
- the latest image that was just built. This is especially important if you are
- using multiple runners that cache images locally. Using the git SHA in your
- image tag makes this less necessary since each job will be unique and you
- shouldn't ever have a stale image, but it's still possible if you re-build a
- given commit after a dependency has changed.
-- You don't want to build directly to `latest` in case there are multiple jobs
- happening simultaneously.
-
[docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
[docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
[2fa]: ../../user/profile/account/two_factor_authentication.md
diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md
index 7d4f28e1f47..66f0d429165 100644
--- a/doc/ci/docker/using_kaniko.md
+++ b/doc/ci/docker/using_kaniko.md
@@ -39,7 +39,7 @@ few important details:
In the following example, kaniko is used to build a Docker image and then push
it to [GitLab Container Registry](../../user/project/container_registry.md).
The job will run only when a tag is pushed. A `config.json` file is created under
-`/root/.docker` with the needed GitLab Container Registry credentials taken from the
+`/kaniko/.docker` with the needed GitLab Container Registry credentials taken from the
[environment variables](../variables/README.md#predefined-variables-environment-variables)
GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the
root directory of the project, builds the Docker image and pushes it to the
@@ -52,8 +52,7 @@ build:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- - mkdir -p /root/.docker
- - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json
+ - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
only:
- tags
diff --git a/doc/ci/examples/test-scala-application.md b/doc/ci/examples/test-scala-application.md
index b3961ec91f3..66bfa41cad9 100644
--- a/doc/ci/examples/test-scala-application.md
+++ b/doc/ci/examples/test-scala-application.md
@@ -25,7 +25,7 @@ before_script:
- apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 642AC823
- apt-get update -y
- apt-get install sbt -y
- - sbt sbt-version
+ - sbt sbtVersion
test:
stage: test
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 83e0fa34ad6..2a179bfbbf0 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -312,7 +312,7 @@ We're always looking for contributions that can mitigate these
If you think that registration token for a Project was revealed, you should
reset them. It's recommended because such token can be used to register another
-Runner to thi Project. It may be next used to obtain the values of secret
+Runner to the Project. It may be next used to obtain the values of secret
variables or clone the project code, that normally may be unavailable for the
attacker.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 24d60a0cdcc..4b2a6ccc7e4 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -681,7 +681,7 @@ Delayed job are for executing scripts after a certain period.
This is useful if you want to avoid jobs entering `pending` state immediately.
You can set the period with `start_in` key. The value of `start_in` key is an elapsed time in seconds, unless a unit is
-provided. `start_key` must be less than or equal to one hour. Examples of valid values include:
+provided. `start_in` key must be less than or equal to one hour. Examples of valid values include:
- `10 seconds`
- `30 minutes`
@@ -2031,3 +2031,5 @@ CI with various languages.
[ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909
[schedules]: ../../user/project/pipelines/schedules.md
[variables-expressions]: ../variables/README.md#variables-expressions
+[ee]: https://about.gitlab.com/gitlab-ee/
+[gitlab-versions]: https://about.gitlab.com/products/ \ No newline at end of file
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 4bbcdc6329f..3fe79943fdc 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -1,55 +1,103 @@
# Code Review Guidelines
-## Getting your merge request reviewed, approved, and merged
+This guide contains advice and best practices for performing code review, and
+having your code reviewed.
+
+All merge requests for GitLab CE and EE, whether written by a GitLab team member
+or a volunteer contributor, must go through a code review process to ensure the
+code is effective, understandable, and maintainable.
-There are a few rules to get your merge request accepted:
+## Getting your merge request reviewed, approved, and merged
-1. Your merge request should only be **merged by a [maintainer][team]**.
- 1. If your merge request includes only backend changes [^1], it must be
- **approved by a [backend maintainer][projects]**.
- 1. If your merge request includes only frontend changes [^1], it must be
- **approved by a [frontend maintainer][projects]**.
- 1. If your merge request includes UX changes [^1], it must
- be **approved by a [UX team member][team]**.
+You are strongly encouraged to get your code **reviewed** by a
+[reviewer](https://about.gitlab.com/handbook/engineering/#reviewer) as soon as
+there is any code to review, to get a second opinion on the chosen solution and
+implementation, and an extra pair of eyes looking for bugs, logic problems, or
+uncovered edge cases. The reviewer can be from a different team, but it is
+recommended to pick someone who knows the domain well. You can read more about the
+importance of involving reviewer(s) in the section on the responsibility of the author below.
+
+If you need some guidance (e.g. it's your first merge request), feel free to ask
+one of the [Merge request coaches][team].
+
+Depending on the areas your merge request touches, it must be **approved** by one
+or more [maintainers](https://about.gitlab.com/handbook/engineering/#maintainer):
+
+ 1. If your merge request includes backend changes [^1], it must be
+ **approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**.
+ 1. If your merge request includes frontend changes [^1], it must be
+ **approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_frontend)**.
+ 1. If your merge request includes UX changes [^1], it must be
+ **approved by a [UX team member][team]**.
1. If your merge request includes adding a new JavaScript library [^1], it must be
**approved by a [frontend lead][team]**.
1. If your merge request includes adding a new UI/UX paradigm [^1], it must be
**approved by a [UX lead][team]**.
- 1. If your merge request includes frontend and backend changes [^1], it must
- be **approved by a [frontend and a backend maintainer][projects]**.
- 1. If your merge request includes UX and frontend changes [^1], it must
- be **approved by a [UX team member and a frontend maintainer][team]**.
- 1. If your merge request includes UX, frontend and backend changes [^1], it must
- be **approved by a [UX team member, a frontend and a backend maintainer][team]**.
- 1. If your merge request includes a new dependency or a filesystem change, it must
- be *approved by a [Distribution team member][team]*. See how to work with the [Distribution team for more details.](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/)
-1. To lower the amount of merge requests maintainers need to review, you can
- ask or assign any [reviewers][projects] for a first review.
- 1. If you need some guidance (e.g. it's your first merge request), feel free
- to ask one of the [Merge request coaches][team].
- 1. It is recommended that you assign a maintainer that is from a different team than your own.
- This ensures that all code across GitLab is consistent and can be easily understood by all contributors.
-
-1. Keep in mind that maintainers are also going to perform a final code review.
- The ideal scenario is that the reviewer has already addressed any concerns
- the maintainer would have found, and the maintainer only has to perform the
- merge, but be prepared for further review comments.
-
-For more guidance, see [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
+ 1. If your merge request includes a new dependency or a filesystem change, it must be
+ **approved by a [Distribution team member][team]**. See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/) for more details.
-## Best practices
+Getting your merge request **merged** also requires a maintainer. If it requires
+more than one approval, the last maintainer to review and approve it will also merge it.
-This guide contains advice and best practices for performing code review, and
-having your code reviewed.
+As described in the section on the responsibility of the maintainer below, you
+are recommended to get your merge request approved and merged by maintainer(s)
+from other teams than your own.
-All merge requests for GitLab CE and EE, whether written by a GitLab team member
-or a volunteer contributor, must go through a code review process to ensure the
-code is effective, understandable, and maintainable.
+### The responsibility of the merge request author
-Any developer can, and is encouraged to, perform code review on merge requests
-of colleagues and contributors. However, the final decision to accept a merge
-request is up to one the project's maintainers, denoted on the
-[engineering projects][projects].
+The responsibility to find the best solution and implement it lies with the
+merge request author.
+
+Before assigning a merge request to a maintainer for approval and merge, they
+should be confident that it actually solves the problem it was meant to solve,
+that it does so in the most appropriate way, that it satisfies all requirements,
+and that there are no remaining bugs, logical problems, or uncovered edge cases.
+The merge request should also have a completed task list in its description and
+a passing CI pipeline to avoid unnecessary back and forth.
+
+To reach the required level of confidence in their solution, an author is expected
+to involve other people in the investigation and implementation processes as
+appropriate.
+
+They are encouraged to reach out to domain experts to discuss different solutions
+or get an implementation reviewed, to product managers and UX designers to clear
+up confusion or verify that the end result matches what they had in mind, to
+database specialists to get input on the data model or specific queries, or to
+any other developer to get an in-depth review of the solution.
+
+If an author is unsure if a merge request needs a domain expert's opinion, that's
+usually a pretty good sign that it does, since without it the required level of
+confidence in their solution will not have been reached.
+
+### The responsibility of the maintainer
+
+Maintainers are responsible for the overall health, quality, and consistency of
+the GitLab codebase, across domains and product areas.
+
+Consequently, their reviews will focus primarily on things like overall
+architecture, code organization, separation of concerns, tests, DRYness,
+consistency, and readability.
+
+Since a maintainer's job only depends on their knowledge of the overall GitLab
+codebase, and not that of any specific domain, they can review, approve and merge
+merge requests from any team and in any product area.
+
+In fact, authors are recommended to get their merge requests merged by maintainers
+from other teams than their own, to ensure that all code across GitLab is consistent
+and can be easily understood by all contributors, from both inside and outside the
+company, without requiring team-specific expertise.
+
+Maintainers will do their best to also review the specifics of the chosen solution
+before merging, but as they are not necessarily domain experts, they may be poorly
+placed to do so without an unreasonable investment of time. In those cases, they
+will defer to the judgment of the author and earlier reviewers and involved domain
+experts, in favor of focusing on their primary responsibilities.
+
+If a developer who happens to also be a maintainer was involved in a merge request
+as a domain expert and/or reviewer, it is recommended that they are not also picked
+as the maintainer to ultimately approve and merge it.
+
+## Best practices
### Everyone
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 29af8dcb9bb..9da4c66933c 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -3,10 +3,12 @@
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is easy for everyone.
+We want to create a welcoming environment for everyone who is interested in contributing. Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our committment to an open and welcoming environment.
+
For a first-time step-by-step guide to the contribution process, please see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
-Looking for something to work on? Look for issues in the [Backlog (Accepting merge requests) milestone](#i-want-to-contribute).
+Looking for something to work on? Look for issues with the label [`Accepting merge requests`](#i-want-to-contribute).
GitLab comes in two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
@@ -30,77 +32,8 @@ vulnerabilities.
## Code of conduct
-### Our Pledge
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, sex characteristics, gender identity and expression,
-level of experience, education, socio-economic status, nationality, personal
-appearance, race, religion, or sexual identity and orientation.
-
-### Our Standards
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-### Our Responsibilities
-
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
-
-### Scope
-
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
-
-### Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at conduct@gitlab.com. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
-
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
-
-### Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
-
-[homepage]: https://www.contributor-covenant.org
+Our code of conduct can be found on the
+["Contributing to GitLab"](https://about.gitlab.com/contributing/) page.
## Closing policy for issues and merge requests
@@ -133,10 +66,10 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
-If you want to contribute to GitLab, [issues in the `Backlog (Accepting merge requests)` milestone][accepting-mrs-weight]
-are a great place to start. Issues with a lower weight (1 or 2) are deemed
-suitable for beginners. These issues will be of reasonable size and challenge,
-for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
+If you want to contribute to GitLab,
+[issues with the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
+are a great place to start.
+If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
please consider we favor
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
@@ -198,4 +131,3 @@ This [documentation](style_guides.md) outlines the current style guidelines.
[team]: https://about.gitlab.com/team/
[getting-help]: https://about.gitlab.com/getting-help/
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
-[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?scope=all&utf8=✓&state=opened&assignee_id=0&milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 3077422ae0a..4661d11b29e 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -181,10 +181,10 @@ Severity levels can be applied further depending on the facet of the impact; e.g
Issues that are beneficial to our users, 'nice to haves', that we currently do
not have the capacity for or want to give the priority to, are labeled as
-~"Accepting Merge Requests", so the community can make a contribution.
+~"Accepting merge requests", so the community can make a contribution.
Community contributors can submit merge requests for any issue they want, but
-the ~"Accepting Merge Requests" label has a special meaning. It points to
+the ~"Accepting merge requests" label has a special meaning. It points to
changes that:
1. We already agreed on,
@@ -192,26 +192,26 @@ changes that:
1. Are likely to get accepted by a maintainer.
We want to avoid a situation when a contributor picks an
-~"Accepting Merge Requests" issue and then their merge request gets closed,
+~"Accepting merge requests" issue and then their merge request gets closed,
because we realize that it does not fit our vision, or we want to solve it in a
different way.
-We add the ~"Accepting Merge Requests" label to:
+We add the ~"Accepting merge requests" label to:
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
solve in the ~"Next Patch Release")
- Small ~"feature proposal"
- Small ~"technical debt" issues
-After adding the ~"Accepting Merge Requests" label, we try to estimate the
+After adding the ~"Accepting merge requests" label, we try to estimate the
[weight](#issue-weight) of the issue. We use issue weight to let contributors
know how difficult the issue is. Additionally:
-- We advertise ["Accepting Merge Requests" issues with weight < 5][up-for-grabs]
+- We advertise [`Accepting merge requests` issues with weight < 5][up-for-grabs]
as suitable for people that have never contributed to GitLab before on the
[Up For Grabs campaign](http://up-for-grabs.net)
- We encourage people that have never contributed to any open source project to
- look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
+ look for [`Accepting merge requests` issues with a weight of 1][firt-timers]
If you've decided that you would like to work on an issue, please @-mention
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
@@ -220,12 +220,12 @@ members to further discuss scope, design, and technical considerations. This wil
ensure that your contribution is aligned with the GitLab product and minimize
any rework and delay in getting it merged into master.
-GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
+GitLab team members who apply the ~"Accepting merge requests" label to an issue
should update the issue description with a responsible product manager, inviting
any potential community contributor to @-mention per above.
-[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
-[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
+[up-for-grabs]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight
+[firt-timers]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight&weight=1
## Issue triaging
@@ -356,6 +356,45 @@ for a release by the appropriate person.
Make sure to mention the merge request that the ~"technical debt" issue or
~"UX debt" issue is associated with in the description of the issue.
+## Technical debt in follow-up issues
+
+It's common to discover technical debt during development of a new feature. In
+the spirit of "minimum viable change", resolution is often deferred to a
+follow-up issue. However, this cannot be used as an excuse to merge poor-quality
+code that would otherwise not pass review, or to overlook trivial matters that
+don't deserve the be scheduled independently, and would be best resolved in the
+original merge request - or not tracked at all!
+
+The overheads of scheduling, and rate of change in the GitLab codebase, mean
+that the cost of a trivial technical debt issue can quickly exceed the value of
+tracking it. This generally means we should resolve these in the original merge
+request - or simply not create a follow-up issue at all.
+
+For example, a typo in a comment that is being copied between files is worth
+fixing in the same MR, but not worth creating a follow-up issue for. Renaming a
+method that is used in many places to make its intent slightly clearer may be
+worth fixing, but it should not happen in the same MR, and is generally not
+worth the overhead of having an issue of its own. These issues would invariably
+be labelled `~P4 ~S4` if we were to create them.
+
+More severe technical debt can have implications for development velocity. If
+it isn't addressed in a timely manner, the codebase becomes needlessly difficult
+to change, new features become difficult to add, and regressions abound.
+
+Discoveries of this kind of technical debt should be treated seriously, and
+while resolution in a follow-up issue may be appropriate, maintainers should
+generally obtain a scheduling commitment from the author of the original MR, or
+the engineering or product manager for the relevant area. This may take the form
+of appropriate Priority / Severity labels on the issue, or an explicit milestone
+and assignee.
+
+The maintainer must always agree before an outstanding discussion is resolved in
+this manner, and will be the one to create the issue. The title and description
+should be of the same quality as those created
+[in the usual manner](#technical-and-ux-debt) - in particular, the issue title
+**must not** begin with `Follow-up`! The creating maintainer should also expect
+to be involved in some capacity when work begins on the follow-up issue.
+
## Stewardship
For issues related to the open source stewardship of GitLab,
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index cc7d8a1e1db..1764e2d8b21 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -2,10 +2,9 @@
We welcome merge requests with fixes and improvements to GitLab code, tests,
and/or documentation. The issues that are specifically suitable for
-community contributions are listed with the
-[`Backlog (Accepting merge requests)` milestone in the CE issue tracker][accepting-mrs-ce]
-and [EE issue tracker][accepting-mrs-ee], but you are free to contribute to any other issue
-you want.
+community contributions are listed with
+[the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors),
+but you are free to contribute to any other issue you want.
Please note that if an issue is marked for the current milestone either before
or while you are working on it, a team member may take over the merge request
@@ -25,8 +24,6 @@ some potentially easy issues.
To start with GitLab development download the [GitLab Development Kit][gdk] and
see the [Development section](../../README.md) for some guidelines.
-[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
-[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 1dcdf788a3e..51f5ddfc1e0 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -451,16 +451,6 @@ preview the changes. The docs URL can be found in two places:
- In the output of the `review-docs-deploy*` job, which also includes the
triggered pipeline so that you can investigate whether something went wrong
-In case the Review App URL returns 404, follow these steps to debug:
-
-1. **Did you follow the URL from the merge request widget?** If yes, then check if
- the link is the same as the one in the job output.
-1. **Did you follow the URL from the job output?** If yes, then it means that
- either the site is not yet deployed or something went wrong with the remote
- pipeline. Give it a few minutes and it should appear online, otherwise you
- can check the status of the remote pipeline from the link in the job output.
- If the pipeline failed or got stuck, drop a line in the `#docs` chat channel.
-
TIP: **Tip:**
Someone that has no merge rights to the CE/EE projects (think of forks from
contributors) will not be able to run the manual job. In that case, you can
@@ -472,6 +462,18 @@ working on. If you don't, the remote docs branch won't be removed either,
and the server where the Review Apps are hosted will eventually be out of
disk space.
+### Troubleshooting review apps
+
+In case the review app URL returns 404, follow these steps to debug:
+
+1. **Did you follow the URL from the merge request widget?** If yes, then check if
+ the link is the same as the one in the job output.
+1. **Did you follow the URL from the job output?** If yes, then it means that
+ either the site is not yet deployed or something went wrong with the remote
+ pipeline. Give it a few minutes and it should appear online, otherwise you
+ can check the status of the remote pipeline from the link in the job output.
+ If the pipeline failed or got stuck, drop a line in the `#docs` chat channel.
+
### Technical aspects
If you want to know the hot details, here's what's really happening:
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index 01068e23082..607ad21d459 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -142,9 +142,49 @@ fancy words. The docs should be clear and easy to understand.
<!-- ## Troubleshooting
Add a troubleshooting guide when possible/applicable. -->
-```
+
+---
Notes:
-- (1): Apply the [tier badges](styleguide.md#product-badges) accordingly
-- (2): Apply the correct format for the [GitLab version introducing the feature](styleguide.md#gitlab-versions-and-tiers)
+- (1): Apply the [tier badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) accordingly
+- (2): Apply the correct format for the [GitLab version introducing the feature](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers)
+```
+
+## Help and feedback section
+
+The "help and feedback" section (introduced by [!319](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/319)) displayed at the end of each document
+can be omitted from the doc by adding a key into the its frontmatter:
+
+```yaml
+---
+feedback: false
+---
+```
+
+The default is to leave it there. If you want to omit it from a document,
+you must check with a technical writer before doing so.
+
+### Disqus
+
+We also have integrated the docs site with Disqus (introduced by
+[!151](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/151)),
+allowing our users to post comments.
+
+To omit only the comments from the feedback section, use the following
+key on the frontmatter:
+
+```yaml
+---
+comments: false
+---
+```
+
+We are only hiding comments in main index pages, such as [the main documentation index](../../README.md), since its content is too broad to comment on. Before omitting Disqus,
+you must check with a technical writer.
+
+Note that once `feedback: false` is added to the frontmatter, it will automatically omit
+Disqus, therefore, don't add both keys to the same document.
+
+The click events in the feedback section are tracked with Google Tag Manager. The
+conversions can be viewed on Google Analytics by navigating to **Behavior > Events > Top events > docs**.
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 0f1f079bdb4..350593cc813 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -112,3 +112,8 @@ feature flag. You can stub a feature flag as follows:
```ruby
stub_feature_flags(my_feature_flag: false)
```
+
+## Enabling a feature flag
+
+Check how to [roll out changes using feature flags](rolling_out_changes_using_feature_flags.md).
+
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 5e13c725e15..f58d79fccf1 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -20,6 +20,7 @@ are very appreciative of the work done by translators and proofreaders!
- French
- Davy Defaud - [GitLab](https://gitlab.com/DevDef), [Crowdin](https://crowdin.com/profile/DevDef)
- German
+ - Michael Hahnle - [GitLab](https://gitlab.com/mhah), [Crowdin](https://crowdin.com/profile/mhah)
- Indonesian
- Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm)
- Italian
@@ -74,6 +75,8 @@ are very appreciative of the work done by translators and proofreaders!
have previously translated.
1. Your request to become a proofreader will be considered on the merits of
- your previous translations.
+ your previous translations by [GitLab team members](https://about.gitlab.com/team/)
+ or [Core team members](https://about.gitlab.com/core-team/) who are fluent in
+ the language or current proofreaders.
[proofreader-src]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/i18n/proofreader.md
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index acbfa1850b4..7727bd74c3c 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -209,6 +209,130 @@ it 'is overdue' do
end
```
+### Pristine test environments
+
+The code exercised by a single GitLab test may access and modify many items of
+data. Without careful preparation before a test runs, and cleanup afterward,
+data can be changed by a test in such a way that it affects the behaviour of
+following tests. This should be avoided at all costs! Fortunately, the existing
+test framework handles most cases already.
+
+When the test environment does get polluted, a common outcome is
+[flaky tests](flaky_tests.md). Pollution will often manifest as an order
+dependency: running spec A followed by spec B will reliably fail, but running
+spec B followed by spec A will reliably succeed. In these cases, you can use
+`rspec --bisect` (or a manual pairwise bisect of spec files) to determine which
+spec is at fault. Fixing the problem requires some understanding of how the test
+suite ensures the environment is pristine. Read on to discover more about each
+data store!
+
+#### SQL database
+
+This is managed for us by the `database_cleaner` gem. Each spec is surrounded in
+a transaction, which is rolled back once the test completes. Certain specs will
+instead issue `DELETE FROM` queries against every table after completion; this
+allows the created rows to be viewed from multiple database connections, which
+is important for specs that run in a browser, or migration specs, among others.
+
+One consequence of using these strategies, instead of the well-known
+`TRUNCATE TABLES` approach, is that primary keys and other sequences are **not**
+reset across specs. So if you create a project in spec A, then create a project
+in spec B, the first will have `id=1`, while the second will have `id=2`.
+
+This means that specs should **never** rely on the value of an ID, or any other
+sequence-generated column. To avoid accidental conflicts, specs should also
+avoid manually specifying any values in these kinds of columns. Instead, leave
+them unspecified, and look up the value after the row is created.
+
+#### Redis
+
+GitLab stores two main categories of data in Redis: cached items, and sidekiq
+jobs.
+
+In most specs, the Rails cache is actually an in-memory store. This is replaced
+between specs, so calls to `Rails.cache.read` and `Rails.cache.write` are safe.
+However, if a spec makes direct Redis calls, it should mark itself with the
+`:clean_gitlab_redis_cache`, `:clean_gitlab_redis_shared_state` or
+`:clean_gitlab_redis_queues` traits as appropriate.
+
+Sidekiq jobs are typically not run in specs, but this behaviour can be altered
+in each spec through the use of `Sidekiq::Testing.inline!` blocks. Any spec that
+causes Sidekiq jobs to be pushed to Redis should use the `:sidekiq` trait, to
+ensure that they are removed once the spec completes.
+
+#### Filesystem
+
+Filesystem data can be roughly split into "repositories", and "everything else".
+Repositories are stored in `tmp/tests/repositories`. This directory is emptied
+before a test run starts, and after the test run ends. It is not emptied between
+specs, so created repositories accumulate within this directory over the
+lifetime of the process. Deleting them is expensive, but this could lead to
+pollution unless carefully managed.
+
+To avoid this, [hashed storage](../../administration/repository_storage_types.md)
+is enabled in the test suite. This means that repositories are given a unique
+path that depends on their project's ID. Since the project IDs are not reset
+between specs, this guarantees that each spec gets its own repository on disk,
+and prevents changes from being visible between specs.
+
+If a spec manually specifies a project ID, or inspects the state of the
+`tmp/tests/repositories/` directory directly, then it should clean up the
+directory both before and after it runs. In general, these patterns should be
+completely avoided.
+
+Other classes of file linked to database objects, such as uploads, are generally
+managed in the same way. With hashed storage enabled in the specs, they are
+written to disk in locations determined by ID, so conflicts should not occur.
+
+Some specs disable hashed storage by passing the `:legacy_storage` trait to the
+`projects` factory. Specs that do this must **never** override the `path` of the
+project, or any of its groups. The default path includes the project ID, so will
+not conflict; but if two specs create a `:legacy_storage` project with the same
+path, they will use the same repository on disk and lead to test environment
+pollution.
+
+Other files must be managed manually by the spec. If you run code that creates a
+`tmp/test-file.csv` file, for instance, the spec must ensure that the file is
+removed as part of cleanup.
+
+#### Persistent in-memory application state
+
+All the specs in a given `rspec` run share the same Ruby process, which means
+they can affect each other by modifying Ruby objects that are accessible between
+specs. In practice, this means global variables, and constants (which includes
+Ruby classes, modules, etc).
+
+Global variables should generally not be modified. If absolutely necessary, a
+block like this can be used to ensure the change is rolled back afterwards:
+
+```ruby
+around(:each) do |example|
+ old_value = $0
+
+ begin
+ $0 = "new-value"
+ example.run
+ ensure
+ $0 = old_value
+ end
+end
+```
+
+If a spec needs to modify a constant, it should use the `stub_const` helper to
+ensure the change is rolled back.
+
+If you need to modify the contents of the `ENV` constant, you can use the
+`stub_env` helper method instead.
+
+While most Ruby **instances** are not shared between specs, **classes**
+and **modules** generally are. Class and module instance variables, accessors,
+class variables, and other stateful idioms, should be treated in the same way as
+global variables - don't modify them unless you have to! In particular, prefer
+using expectations, or dependency injection along with stubs, to avoid the need
+for modifications. If you have no other choice, an `around` block similar to the
+example for global variables, above, can be used, but this should be avoided if
+at all possible.
+
### Table-based / Parameterized tests
This style of testing is used to exercise one piece of code with a comprehensive
@@ -348,6 +472,37 @@ GitLab uses [factory_bot] as a test fixture replacement.
All fixtures should be be placed under `spec/fixtures/`.
+### Repositories
+
+Testing some functionality, e.g., merging a merge request, requires a git
+repository with a certain state to be present in the test environment. GitLab
+maintains the [gitlab-test](https://gitlab.com/gitlab-org/gitlab-test)
+repository for certain common cases - you can ensure a copy of the repository is
+used with the `:repository` trait for project factories:
+
+```ruby
+let(:project) { create(:project, :repository) }
+```
+
+Where you can, consider using the `:custom_repo` trait instead of `:repository`.
+This allows you to specify exactly what files will appear in the `master` branch
+of the project's repository. For example:
+
+```ruby
+let(:project) do
+ create(
+ :project, :custom_repo,
+ files: {
+ 'README.md' => 'Content here',
+ 'foo/bar/baz.txt' => 'More content here'
+ }
+ )
+end
+```
+
+This will create a repository containing two files, with default permissions and
+the specified content.
+
### Config
RSpec config files are files that change the RSpec config (i.e.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 9e2e58657f1..1210ac58499 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -460,11 +460,11 @@ GitLab-Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is o
### Install Gitaly
# Fetch Gitaly source with Git and compile with Go
- sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production
+ sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
You can specify a different Git repository by providing it as an extra parameter:
- sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,https://example.com/gitaly.git]" RAILS_ENV=production
+ sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
Next, make sure gitaly configured:
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index e2eea57d694..a7470d27b4b 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -339,6 +339,23 @@ args: {
}
```
+### `uid_attribute`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/43806) in GitLab 10.7.
+
+By default, the `uid` is set as the `name_id` in the SAML response. If you'd like to designate a unique attribute for the `uid`, you can set the `uid_attribute`. In the example below, the value of `uid` attribute in the SAML response is set as the `uid_attribute`.
+
+```yaml
+args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ uid_attribute: 'uid'
+}
+```
+
## Troubleshooting
### 500 error after login
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index a4f17746b69..2e8380aa5d8 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -83,7 +83,7 @@ sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workh
```bash
cd /home/git/gitlab
-sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production
+sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
```
### 6. Update gitlab-shell to the corresponding version
diff --git a/doc/user/group/img/access_requests_management.png b/doc/user/group/img/access_requests_management.png
index 36deaa89a70..7de6a1c0a5e 100644
--- a/doc/user/group/img/access_requests_management.png
+++ b/doc/user/group/img/access_requests_management.png
Binary files differ
diff --git a/doc/user/group/img/add_new_members.png b/doc/user/group/img/add_new_members.png
index 99b8e52ea13..4431c9fbe0b 100644
--- a/doc/user/group/img/add_new_members.png
+++ b/doc/user/group/img/add_new_members.png
Binary files differ
diff --git a/doc/user/group/img/create_new_group_info.png b/doc/user/group/img/create_new_group_info.png
index 1ac26fb08d9..c2e6ed43c5b 100644
--- a/doc/user/group/img/create_new_group_info.png
+++ b/doc/user/group/img/create_new_group_info.png
Binary files differ
diff --git a/doc/user/group/img/create_new_project_from_group.png b/doc/user/group/img/create_new_project_from_group.png
index 553cd0759aa..b6286ac7800 100644
--- a/doc/user/group/img/create_new_project_from_group.png
+++ b/doc/user/group/img/create_new_project_from_group.png
Binary files differ
diff --git a/doc/user/group/img/group_settings.png b/doc/user/group/img/group_settings.png
index 1705bf4ce8e..f3a75f1bde8 100644
--- a/doc/user/group/img/group_settings.png
+++ b/doc/user/group/img/group_settings.png
Binary files differ
diff --git a/doc/user/group/img/request_access_button.png b/doc/user/group/img/request_access_button.png
index 54b490a3bb2..4d73990ec7e 100644
--- a/doc/user/group/img/request_access_button.png
+++ b/doc/user/group/img/request_access_button.png
Binary files differ
diff --git a/doc/user/group/img/select_group_dropdown.png b/doc/user/group/img/select_group_dropdown.png
index 79eca5d94d5..8c03ffffbde 100644
--- a/doc/user/group/img/select_group_dropdown.png
+++ b/doc/user/group/img/select_group_dropdown.png
Binary files differ
diff --git a/doc/user/group/img/withdraw_access_request_button.png b/doc/user/group/img/withdraw_access_request_button.png
index 4365f7fa788..a5fe78eb090 100644
--- a/doc/user/group/img/withdraw_access_request_button.png
+++ b/doc/user/group/img/withdraw_access_request_button.png
Binary files differ
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 7d01c6f2bf6..d673fa4d21a 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -252,6 +252,13 @@ level of members in group.
Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock).
+#### Group-level file templates **[PREMIUM]**
+
+Group-level file templates allow you to share a set of templates for common file
+types with every project in a group.
+
+Learn more about [Group-level file templates](https://docs.gitlab.com/ee/user/group/index.html#group-level-file-templates-premium).
+
### Advanced settings
- **Projects**: view all projects within that group, add members to each project,
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index bc6ecdf4f32..64219737d61 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -2,8 +2,8 @@
Two-factor Authentication (2FA) provides an additional level of security to your
GitLab account. Once enabled, in addition to supplying your username and
-password to login, you'll be prompted for a code generated by your one time password
-authenticator. For example, a password manager on one of your devices.
+password to login, you'll be prompted for a code generated by your one time password
+authenticator. For example, a password manager on one of your devices.
By enabling 2FA, the only way someone other than you can log into your account
is to know your username and password *and* have access to your one time password secret.
@@ -83,9 +83,11 @@ Click on **Register U2F Device** to complete the process.
Recovery codes are not generated for U2F devices.
Should you ever lose access to your one time password authenticator, you can use one of the ten provided
-backup codes to login to your account. We suggest copying or printing them for
-storage in a safe place. **Each code can be used only once** to log in to your
-account.
+backup codes to login to your account. We suggest copying them, printing them, or downloading them using
+the **Download codes** button for storage in a safe place.
+
+CAUTION: **Caution:**
+Each code can be used only once to log in to your account.
If you lose the recovery codes or just want to generate new ones, you can do so
[using SSH](#generate-new-recovery-codes-using-ssh).
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 25d6c34409c..7d55048c994 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -45,16 +45,14 @@ the following table.
| Scope | Description |
| ----- | ----------- |
|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). |
-| `api` | Grants complete access to the API (read/write) ([introduced][ce-5951] in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. |
-| `read_registry` | Allows to read [container registry] images if a project is private and authorization is required ([introduced][ce-11845] in GitLab 9.3). |
+| `api` | Grants complete access to the API and Container Registry (read/write) ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. |
+| `read_registry` | Allows to read (pull) [container registry] images if a project is private and authorization is required ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 9.3). |
| `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). |
-| `read_repository` | Allows read-access to the repository through git clone. |
+| `read_repository` | Allows read-access (pull) to the repository through git clone. |
[2fa]: ../account/two_factor_authentication.md
[api]: ../../api/README.md
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
-[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
-[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
[ce-14838]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 3ec17806490..48004471f0a 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -215,7 +215,7 @@ twice, which can lead to confusion during deployments.
| [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] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
-| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with [Rubix](https://github.com/amit1rrr/rubix). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
+| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found at [Nurtch Documentation](http://docs.nurtch.com/en/latest). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
## Getting the external IP address
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 2709ebb6f05..1b1827a2658 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -119,12 +119,17 @@ and [Using the GitLab Container Registry documentation](../../ci/docker/using_do
> Project Deploy Tokens were [introduced][ce-17894] in GitLab 10.7
If a project is private, credentials will need to be provided for authorization.
-The preferred way to do this, is either by using a [personal access tokens][pat] or a [project deploy token][pdt].
+There are two ways to do this:
+
+- By using a [personal access token](../profile/personal_access_tokens.md).
+- By using a [deploy token](../project/deploy_tokens/index.md).
+
The minimal scope needed for both of them is `read_registry`.
-Example of using a personal access token:
-```
-docker login registry.example.com -u <your_username> -p <your_access_token>
+Example of using a token:
+
+```sh
+docker login registry.example.com -u <username> -p <token>
```
## Troubleshooting the GitLab Container Registry
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index ff647b2f0a2..dc73194309c 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -9,7 +9,7 @@ at midnight UTC and that they can be only managed by [maintainers](https://docs.
## Creating a Deploy Token
-You can create as many deploy tokens as you like from the settings of your project:
+You can create as many deploy tokens as you like from the settings of your project:
1. Log in to your GitLab account.
1. Go to the project you want to create Deploy Tokens for.
@@ -49,14 +49,13 @@ To download a repository using a Deploy Token, you just need to:
2. Take note of your `username` and `token`
3. `git clone` the project using the Deploy Token:
+ ```sh
+ git clone http://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git
+ ```
-```bash
-git clone https://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git
-```
-
-Just replace `<username>` and `<deploy_token>` with the proper values
+Replace `<username>` and `<deploy_token>` with the proper values.
-### Read container registry images
+### Read Container Registry images
To read the container registry images, you'll need to:
@@ -64,21 +63,29 @@ To read the container registry images, you'll need to:
2. Take note of your `username` and `token`
3. Log in to GitLab’s Container Registry using the deploy token:
-```
+```sh
docker login registry.example.com -u <username> -p <deploy_token>
```
-Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply
+Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply
pull images from your Container Registry.
### GitLab Deploy Token
> [Introduced][ce-18414] in GitLab 10.8.
-There's a special case when it comes to Deploy Tokens, if a user creates one
-named `gitlab-deploy-token`, the username and token of the Deploy Token will be
-automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and
-`CI_DEPLOY_PASSWORD`, respectively.
+There's a special case when it comes to Deploy Tokens. If a user creates one
+named `gitlab-deploy-token`, the username and token of the Deploy Token will be
+automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and
+`CI_DEPLOY_PASSWORD`, respectively. With the GitLab Deploy Token, the
+`read_registry` scope is implied.
+
+After you create the token, you can login to the Container Registry using
+those variables:
+
+```sh
+docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY
+```
[ce-17894]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894
[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md
index 631f511b5fa..d78721f8658 100644
--- a/doc/user/project/issues/issues_functionalities.md
+++ b/doc/user/project/issues/issues_functionalities.md
@@ -118,9 +118,12 @@ is the `project-name`, and `xxx` is the issue number.
#### 13. @mentions
-- Mentions: you can either `@mention` a user or a group present in your
-GitLab instance and they will be notified via todos and email, unless that
-person has disabled all notifications in their profile settings.
+- You can either `@mention` a user or a group present in your
+ GitLab instance and they will be notified via todos and email, unless that
+ person has disabled all notifications in their profile settings.
+- Mentions for yourself (the current logged in user),will be distinctly highlighted
+ in a different color, allowing you to easily see which comments involve you,
+ helping you focus on them quickly.
To change your [notification settings](../../../workflow/notifications.md) navigate to
**Profile Settings** > **Notifications** > **Global notification level**
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 632253db94c..3cf46231a9d 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -68,7 +68,8 @@ From [project issue boards](../issue_board.md), you can filter by both group mil
When filtering by milestone, in addition to choosing a specific project milestone or group milestone, you can choose a special milestone filter.
-- **No Milestone**: Show issues or merge requests with no assigned milestone.
+- **None**: Show issues or merge requests with no assigned milestone.
+- **Any**: Show issues or merge requests that have an assigned milestone.
- **Upcoming**: Show issues or merge requests that have been assigned the open milestone that has the next upcoming due date (i.e. nearest due date in the future).
- **Started**: Show issues or merge requests that have an assigned milestone with a start date that is before today.
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index c2f53540089..0a4542b71ed 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -24,6 +24,7 @@ discussions, and descriptions:
| `/reopen` | Reopen | ✓ | ✓ |
| `/title <New title>` | Change title | ✓ | ✓ |
| `/award :emoji:` | Toggle emoji award | ✓ | ✓ |
+| `/assign me` | Assign yourself | ✓ | ✓ |
| `/assign @user` | Assign one user | ✓ | ✓ |
| `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | |
| `/unassign` | Remove assignee(s) | ✓ | ✓ |
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index e1d8345f415..783081cec26 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -30,12 +30,12 @@ to learn more.
## Delete merged branches
-> [Introduced][ce-6449] in GitLab 8.14.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449) in GitLab 8.14.
![Delete merged branches](img/delete_merged_branches.png)
This feature allows merged branches to be deleted in bulk. Only branches that
-have been merged and [are not protected][protected] will be deleted as part of
+have been merged and [are not protected](../../protected_branches.md) will be deleted as part of
this operation.
It's particularly useful to clean up old branches that were not deleted
@@ -44,7 +44,7 @@ automatically when a merge request was merged.
## Branch filter search box
-> [Introduced][https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166] in GitLab 11.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166) in GitLab 11.5.
![Branch filter search box](img/branch_filter_search_box.png)
@@ -57,6 +57,3 @@ Sometimes when you have hundreds of branches you may want a more flexible matchi
- `^feature` will only match branch names that begin with 'feature'.
- `feature$` will only match branch names that end with 'feature'.
-
-[ce-6449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449 "Add button to delete all merged branches"
-[protected]: ../../protected_branches.md
diff --git a/doc/workflow/img/repository_mirroring_force_update.png b/doc/workflow/img/repository_mirroring_force_update.png
new file mode 100644
index 00000000000..8ba715d1ba3
--- /dev/null
+++ b/doc/workflow/img/repository_mirroring_force_update.png
Binary files differ
diff --git a/doc/workflow/img/repository_mirroring_pull_settings_lower.png b/doc/workflow/img/repository_mirroring_pull_settings_lower.png
new file mode 100644
index 00000000000..a3e0b74ddf8
--- /dev/null
+++ b/doc/workflow/img/repository_mirroring_pull_settings_lower.png
Binary files differ
diff --git a/doc/workflow/img/repository_mirroring_pull_settings_upper.png b/doc/workflow/img/repository_mirroring_pull_settings_upper.png
new file mode 100644
index 00000000000..c60354fdca7
--- /dev/null
+++ b/doc/workflow/img/repository_mirroring_pull_settings_upper.png
Binary files differ
diff --git a/doc/workflow/img/repository_mirroring_push_settings.png b/doc/workflow/img/repository_mirroring_push_settings.png
new file mode 100644
index 00000000000..21a6aca4526
--- /dev/null
+++ b/doc/workflow/img/repository_mirroring_push_settings.png
Binary files differ
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 8c4e6ea8eab..4225d1aa31d 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -1,351 +1,394 @@
# Repository mirroring
-Repository mirroring is a way to mirror repositories from external sources.
-It can be used to mirror all branches, tags, and commits that you have
-in your repository.
+Repository mirroring allows for mirroring of repositories to and from external sources. It can be
+used to mirror branches, tags, and commits between repositories.
-Your mirror at GitLab will be updated automatically. You can
-also manually trigger an update at most once every 5 minutes.
+A repository mirror at GitLab will be updated automatically. You can also manually trigger an update
+at most once every 5 minutes.
## Overview
-Repository mirroring is very useful when, for some reason, you must use a
-project from another source.
+Repository mirroring is useful when you want to use a repository outside of GitLab.
-There are two kinds of repository mirroring features supported by GitLab:
-**push** and **pull**, the latter being only available in GitLab Enterprise Edition.
-The **push** method mirrors the repository in GitLab to another location.
+There are two kinds of repository mirroring supported by GitLab:
-Once the mirror repository is updated, all new branches,
-tags, and commits will be visible in the project's activity feed.
-Users with at least [developer access][perms] to the project can also force an
-immediate update with the click of a button. This button will not be available if
-the mirror is already being updated or 5 minutes still haven't passed since its last update.
+- Push: for mirroring a GitLab repository to another location.
+- Pull: for mirroring a repository from another location to GitLab. **[STARTER]**
-A few things/limitations to consider:
+When the mirror repository is updated, all new branches, tags, and commits will be visible in the
+project's activity feed.
-- The repository must be accessible over `http://`, `https://`, `ssh://` or `git://`.
-- If your HTTP repository is not publicly accessible, add authentication
- information to the URL, like: `https://username@gitlab.company.com/group/project.git`.
- In some cases, you might need to use a personal access token instead of a
- password, e.g., you want to mirror to GitHub and have 2FA enabled.
-- The import will time out after 15 minutes. For repositories that take longer
- use a clone/push combination.
-- The Git LFS objects will not be synced. You'll need to push/pull them
- manually.
+Users with at least [developer access](../user/permissions.md) to the project can also force an
+immediate update, unless:
+
+- The mirror is already being updated.
+- 5 minutes haven't elapsed since its last update.
## Use cases
-- You migrated to GitLab but still need to keep your project in another source.
- In that case, you can simply set it up to mirror to GitLab (pull) and all the
- essential history of commits, tags and branches will be available in your
- GitLab instance.
-- You have old projects in another source that you don't use actively anymore,
- but don't want to remove for archiving purposes. In that case, you can create
- a push mirror so that your active GitLab repository can push its changes to the
- old location.
+The following are some possible use cases for repository mirroring:
-## Pulling from a remote repository **[STARTER]**
+- You migrated to GitLab but still need to keep your project in another source. In that case, you
+ can simply set it up to mirror to GitLab (pull) and all the essential history of commits, tags,
+ and branches will be available in your GitLab instance. **[STARTER]**
+- You have old projects in another source that you don't use actively anymore, but don't want to
+ remove for archiving purposes. In that case, you can create a push mirror so that your active
+ GitLab repository can push its changes to the old location.
->[Introduced][ee-51] in GitLab Enterprise Edition 8.2.
+## Pushing to a remote repository **[CORE]**
-You can set up a repository to automatically have its branches, tags, and commits
-updated from an upstream repository. This is useful when a repository you're
-interested in is located on a different server, and you want to be able to
-browse its content and its activity using the familiar GitLab interface.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in GitLab Enterprise
+> Edition 8.7. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
-When creating a new project, you can enable repository mirroring when you choose
-to import the repository from "Any repo by URL". Enter the full URL of the Git
-repository to pull from and click on the **Mirror repository** checkbox.
+For an existing project, you can set up push mirroring as follows:
-![New project](repository_mirroring/repository_mirroring_new_project.png)
+1. Navigate to your project's **Settings > Repository** and expand the **Mirroring repositories** section.
+1. Enter a repository URL.
+1. Select **Push** from the **Mirror direction** dropdown.
+1. Select an authentication method from the **Authentication method** dropdown, if necessary.
+1. Check the **Only mirror protected branches** box, if necessary.
+1. Click the **Mirror repository** button to save the configuration.
-For an existing project, you can set up mirror pulling by visiting your project's
-**Settings âž” Repository** and searching for the "Pull from a remote repository"
-section. Check the "Mirror repository" box and hit **Save changes** at the bottom.
-You have a few options to choose from one being the user who will be the author
-of all events in the activity feed that are the result of an update. This user
-needs to have at least [master access][perms] to the project. Another option is
-whether you want to trigger builds for mirror updates.
+![Repository mirroring push settings screen](img/repository_mirroring_push_settings.png)
-![Pull settings](repository_mirroring/repository_mirroring_pull_settings.png)
+When push mirroring is enabled, only push commits directly to the mirrored repository to prevent the
+mirror diverging. All changes will end up in the mirrored repository whenever:
-Since the repository on GitLab functions as a mirror of the upstream repository,
-you are advised not to push commits directly to the repository on GitLab.
-Instead, any commits should be pushed to the upstream repository, and will end
-up in the GitLab repository automatically within a certain period of time
-or when a [forced update](#forcing-an-update) is initiated.
+- Commits are pushed to GitLab.
+- A [forced update](#forcing-an-update) is initiated.
-If you do manually update a branch in the GitLab repository, the branch will
-become diverged from upstream, and GitLab will no longer automatically update
-this branch to prevent any changes from being lost.
+Changes pushed to files in the repository are automatically pushed to the remote mirror at least:
-![Diverged branch](repository_mirroring/repository_mirroring_diverged_branch.png)
+- Within five minutes of being received.
+- Within one minute if **Only mirror protected branches** is enabled.
-### Trigger update using API **[STARTER]**
+In the case of a diverged branch, you will see an error indicated at the **Mirroring repositories**
+section.
+
+### Push only protected branches **[CORE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350) in
+> [GitLab Starter](https://about.gitlab.com/pricing/) 10.3. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
->[Introduced][ee-3453] in GitLab Enterprise Edition 10.3.
+You can choose to only push your protected branches from GitLab to your remote repository.
-Pull mirroring uses polling to detect new branches and commits added upstream,
-often many minutes afterwards. If you notify GitLab by [API][pull-api], updates
-will be pulled immediately.
+To use this option, check the **Only mirror protected branches** box when creating a repository
+mirror.
-Read the [Pull Mirror Trigger API docs][pull-api].
+## Setting up a push mirror from GitLab to GitHub **[CORE]**
-### Pull only protected branches **[STARTER]**
+To set up a mirror from GitLab to GitHub, you need to follow these steps:
->[Introduced][ee-3326] in GitLab Enterprise Edition 10.3.
+1. Create a [GitHub personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) with the `public_repo` box checked.
+1. Fill in the **Git repository URL** field, with the personal access token instead of a password.
+ For example: `https://<GitHubUsername>:<GitHubPersonalAccessToken>@github.com/group/project.git`.
+1. Click the **Mirror repository** button.
+1. Wait, or click the update button.
-You can choose to only pull the protected branches from your remote repository to GitLab.
+## Pulling from a remote repository **[STARTER]**
-To use this option go to your project's repository settings page under pull mirror.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2.
-### Overwrite diverged branches **[STARTER]**
+You can set up a repository to automatically have its branches, tags, and commits updated from an
+upstream repository.
->[Introduced][ee-4559] in GitLab Enterprise Edition 10.6.
+This is useful when a repository you're interested in is located on a different server, and you want
+to be able to browse its content and its activity using the familiar GitLab interface.
-You can choose to always update your local branch with the remote version even
-if your local version has diverged from the remote.
+To configure mirror pulling for an existing project:
-To use this option go to your project's repository settings page under pull mirror.
+1. Navigate to your project's **Settings > Repository** and expand the **Mirroring repositories**
+ section.
+1. Enter a repository URL.
+1. Select **Pull** from the **Mirror direction** dropdown.
+1. Select an authentication method from the **Authentication method** dropdown, if necessary.
+1. If necessary, check the following boxes:
+ - **Overwrite diverged branches**.
+ - **Trigger pipelines for mirror updates**.
+ - **Only mirror protected branches**.
+1. Click the **Mirror repository** button to save the configuration.
-### Hard failure **[STARTER]**
+![Repository mirroring pull settings screen - upper part](img/repository_mirroring_pull_settings_upper.png)
->[Introduced][ee-3117] in GitLab Enterprise Edition 10.2.
+---
-Once a mirror gets retried 14 times in a row, it will get marked as hard failed,
-this will become visible in either the project main dashboard or in the
-pull mirror settings page.
+![Repository mirroring pull settings screen - lower part](img/repository_mirroring_pull_settings_lower.png)
-![Hard failed mirror main notice](repository_mirroring/repository_mirroring_hard_failed_main.png)
+Because GitLab is now set to pull changes from the upstream repository, you should not push commits
+directly to the repository on GitLab. Instead, any commits should be pushed to the upstream repository.
+Changes pushed to the upstream repository will be pulled into the GitLab repository, either:
-![Hard failed mirror settings notice](repository_mirroring/repository_mirroring_hard_failed_settings.png)
+- Automatically within a certain period of time.
+- When a [forced update](#forcing-an-update) is initiated.
-When a project is hard failed, it will no longer get picked up for mirroring.
-A user can resume the project mirroring again by either [forcing an update](#forcing-an-update)
-or by changing the import URL in repository settings.
+CAUTION: **Caution:**
+If you do manually update a branch in the GitLab repository, the branch will become diverged from
+upstream and GitLab will no longer automatically update this branch to prevent any changes from being lost.
+
+### How it works
+
+Once you activate the pull mirroring feature, the mirror will be inserted into a queue. A scheduler
+will start every minute and schedule a fixed number of mirrors for update, based on the configured maximum capacity.
+
+If the mirror updates successfully, it will be enqueued once again with a small backoff period.
+
+If the mirror fails (for example, a branch diverged from upstream), the project's backoff period is
+increased each time it fails, up to a maximum amount of time.
### SSH authentication **[STARTER]**
-> [Introduced][ee-2551] in GitLab Starter 9.5
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.5.
-If you're mirroring over SSH (i.e., an `ssh://` URL), you can authenticate using
-password-based authentication, just as over HTTPS, but you can also use public
-key authentication. This is often more secure than password authentication,
-especially when the source repository supports [Deploy Keys][deploy-key].
+SSH authentication is mutual:
-To get started, navigate to **Settings âž” Repository âž” Pull from a remote repository**,
-enable mirroring (if not already enabled) and enter an `ssh://` URL.
+- You have to prove to the server that you're allowed to access the repository.
+- The server also has to prove to *you* that it's who it claims to be.
-> **NOTE**: SCP-style URLs, e.g., `git@example.com:group/project.git`, are not
-supported at this time.
+You provide your credentials as a password or public key. The server that the source repository
+resides on provides its credentials as a "host key", the fingerprint of which needs to be verified manually.
-Entering the URL adds two features to the page - `Fingerprints` and
-`SSH public key authentication`:
+If you're mirroring over SSH (that is, using an `ssh://` URL), you can authenticate using:
-![Pull settings for SSH](repository_mirroring/repository_mirroring_pull_settings_for_ssh.png)
+- Password-based authentication, just as over HTTPS.
+- Public key authentication. This is often more secure than password authentication, especially when
+ the source repository supports [Deploy Keys](../ssh/README.md#deploy-keys).
-SSH authentication is mutual. You have to prove to the server that you're
-allowed to access the repository, but the server also has to prove to *you* that
-it's who it claims to be. You provide your credentials as a password or public
-key. The server that the source repository resides on provides its credentials
-as a "host key", the fingerprint of which needs to be verified manually.
+To get started:
-Press the `Detect host keys` button. GitLab will fetch the host keys from the
-server, and display the fingerprints to you:
+1. Navigate to your project's **Settings > Repository** and expand the **Mirroring repositories** section.
+1. Enter an `ssh://` URL for mirroring.
-![Detect SSH host keys](repository_mirroring/repository_mirroring_detect_host_keys.png)
+NOTE: **Note:**
+SCP-style URLs (that is, `git@example.com:group/project.git`) are not supported at this time.
+
+Entering the URL adds two buttons to the page:
+
+- **Detect host keys**.
+- **Input host keys manually**.
+
+If you click the:
+
+- **Detect host keys** button, GitLab will fetch the host keys from the server and display the fingerprints.
+- **Input host keys manually** button, a field is displayed where you can paste in host keys.
You now need to verify that the fingerprints are those you expect. GitLab.com
and other code hosting sites publish their fingerprints in the open for you
to check:
-* [AWS CodeCommit](http://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-fingerprints)
-* [Bitbucket](https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints)
-* [GitHub](https://help.github.com/articles/github-s-ssh-key-fingerprints/)
-* [GitLab.com](https://about.gitlab.com/gitlab-com/settings/#ssh-host-keys-fingerprints)
-* [Launchpad](https://help.launchpad.net/SSHFingerprints)
-* [Savannah](http://savannah.gnu.org/maintenance/SshAccess/)
-* [SourceForge](https://sourceforge.net/p/forge/documentation/SSH%20Key%20Fingerprints/)
+- [AWS CodeCommit](http://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-fingerprints)
+- [Bitbucket](https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints)
+- [GitHub](https://help.github.com/articles/github-s-ssh-key-fingerprints/)
+- [GitLab.com](https://about.gitlab.com/gitlab-com/settings/#ssh-host-keys-fingerprints)
+- [Launchpad](https://help.launchpad.net/SSHFingerprints)
+- [Savannah](http://savannah.gnu.org/maintenance/SshAccess/)
+- [SourceForge](https://sourceforge.net/p/forge/documentation/SSH%20Key%20Fingerprints/)
-Other providers will vary. If you're running on-premises GitLab, or otherwise
+Other providers will vary. If you're running self-managed GitLab, or otherwise
have access to the source server, you can securely gather the key fingerprints:
-```
+```sh
$ cat /etc/ssh/ssh_host*pub | ssh-keygen -E md5 -l -f -
256 MD5:f4:28:9f:23:99:15:21:1b:bf:ed:1f:8e:a0:76:b2:9d root@example.com (ECDSA)
256 MD5:e6:eb:45:8a:3c:59:35:5f:e9:5b:80:12:be:7e:22:73 root@example.com (ED25519)
2048 MD5:3f:72:be:3d:62:03:5c:62:83:e8:6e:14:34:3a:85:1d root@example.com (RSA)
```
-(You may need to exclude `-E md5` for some older versions of SSH).
+NOTE: **Note:**
+You may need to exclude `-E md5` for some older versions of SSH.
-If you're an SSH expert and already have a `known_hosts` file you'd like to use
-unaltered, then you can skip these steps. Just press the "Show advanced" button
-and paste in the file contents:
+When pulling changes from the source repository, GitLab will now check that at least one of the stored
+host keys matches before connecting. This can prevent malicious code from being injected into your
+mirror, or your password being stolen.
-![Advanced SSH host key management](repository_mirroring/repository_mirroring_pull_advanced_host_keys.png)
+### SSH public key authentication
-Once you've **carefully verified** that all the fingerprints match your trusted
-source, you can press `Save changes`. This will record the host keys, along with
-the person who verified them (you!) and the date:
+To use SSH public key authentication, you'll also need to choose that option from the **Authentication method**
+dropdown. GitLab will generate a 4096-bit RSA key and display the public component of that key to you.
-![SSH host keys submitted](repository_mirroring/repository_mirroring_ssh_host_keys_verified.png)
+You then need to add the public SSH key to the source repository configuration. If:
-When pulling changes from the source repository, GitLab will now check that at
-least one of the stored host keys matches before connecting. This can prevent
-malicious code from being injected into your mirror, or your password being
-stolen!
+- The source is hosted on GitLab, you should add the public SSH key as a [Deploy Key](../ssh/README.md#deploy-keys).
+- The source is hosted elsewhere, you may need to add the key to your user's `authorized_keys` file.
+ Paste the entire public SSH key into the file on its own line and save it.
-To use SSH public key authentication, you'll also need to choose that option
-from the authentication methods dropdown. GitLab will generate a 4096-bit RSA
-key and display the public component of that key to you:
+Once the public key is set up on the source repository, click the **Mirror repository** button and
+your mirror will begin working.
-![SSH public key authentication](repository_mirroring/repository_mirroring_ssh_public_key_authentication.png)
+If you need to change the key at any time, you can click the **Regenerate key** button to do so. You'll have to update the source repository with the new key to keep the mirror running.
-You then need to add the public SSH key to the source repository configuration.
-If the source is hosted on GitLab, you should add it as a [Deploy Key][deploy-key].
-Other sources may require you to add the key to your user's `authorized_keys`
-file - just paste the entire `ssh-rsa AAA.... user@host` block into the file on
-its own line and save it.
-
-Once the public key is set up on the source repository, press `Save changes` and your
-mirror will begin working.
-
-If you need to change the key at any time, you can press the `Regenerate key`
-button to do so. You'll have to update the source repository with the new key
-to keep the mirror running.
-
-### How it works
-
-Once you activate the pull mirroring feature, the mirror will be inserted into
-a queue. A scheduler will start every minute and schedule a fixed amount of
-mirrors for update, based on the configured maximum capacity.
-
-If the mirror successfully updates it will be enqueued once again with a small
-backoff period.
-
-If the mirror fails (eg: branch diverged from upstream), the project's backoff
-period will be penalized each time it fails up to a maximum amount of time.
-
-## Pushing to a remote repository
-
->[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in
-GitLab Enterprise Edition 8.7. [Moved to GitLab Community Edition][ce-18715] in 10.8.
-
-For an existing project, you can set up push mirror from your project's
-**Settings âž” Repository** and searching for the "Push to a remote repository"
-section. Check the "Remote mirror repository" box and fill in the Git URL of
-the repository to push to. Click **Save changes** for the changes to take
-effect.
+### Overwrite diverged branches **[STARTER]**
-![Push settings](repository_mirroring/repository_mirroring_push_settings.png)
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4559) in
+> [GitLab Starter](https://about.gitlab.com/pricing/) 10.6.
-When push mirroring is enabled, you are advised not to push commits directly
-to the mirrored repository to prevent the mirror diverging.
-All changes will end up in the mirrored repository whenever commits
-are pushed to GitLab, or when a [forced update](#forcing-an-update) is
-initiated.
+You can choose to always update your local branches with remote versions, even if they have
+diverged from the remote.
-Pushes into GitLab are automatically pushed to the remote mirror at least once
-every 5 minutes after they are received or once every minute if **push only
-protected branches** is enabled.
+CAUTION: **Caution:**
+For mirrored branches, enabling this option results in the loss of local changes.
-In case of a diverged branch, you will see an error indicated at the **Mirror
-repository** settings.
+To use this option, check the **Overwrite diverged branches** box when creating a repository mirror.
-![Diverged branch](
-repository_mirroring/repository_mirroring_diverged_branch_push.png)
+### Only mirror protected branches **[STARTER]**
-### Push only protected branches
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326) in
+> [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
->[Introduced][ee-3350] in GitLab Enterprise Edition 10.3. [Moved to GitLab Community Edition][ce-18715] in 10.8.
+You can choose to pull mirror only the protected branches from your remote repository to GitLab.
+Non-protected branches are not mirrored and can diverge.
-You can choose to only push your protected branches from GitLab to your remote repository.
+To use this option, check the **Only mirror protected branches** box when creating a repository mirror.
-To use this option go to your project's repository settings page under push mirror.
+### Hard failure **[STARTER]**
-## Setting up a push mirror from GitLab to GitHub
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117) in
+> [GitLab Starter](https://about.gitlab.com/pricing/) 10.2.
-To set up a mirror from GitLab to GitHub, you need to follow these steps:
+Once the mirroring process is unsuccessfully retried 14 times in a row, it will get marked as hard
+failed. This will become visible in either the:
-1. Create a [GitHub personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) with the "public_repo" box checked:
+- Project's main dashboard.
+- Pull mirror settings page.
- ![edit personal access token GitHub](repository_mirroring/repository_mirroring_github_edit_personal_access_token.png)
+When a project is hard failed, it will no longer get picked up for mirroring. A user can resume the
+project mirroring again by [Forcing an update](#forcing-an-update).
-1. Fill in the "Git repository URL" with the personal access token replacing the password `https://GitHubUsername:GitHubPersonalAccessToken@github.com/group/project.git`:
+### Trigger update using API **[STARTER]**
- ![push to remote repo](repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.png)
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453) in
+[GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
-1. Save
-1. And either wait or trigger the "Update Now" button:
+Pull mirroring uses polling to detect new branches and commits added upstream, often minutes
+afterwards. If you notify GitLab by [API](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project),
+updates will be pulled immediately.
+
+For more information, see [Start the pull mirroring process for a Project](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project).
+
+## Forcing an update **[CORE]**
+
+While mirrors are scheduled to update automatically, you can always force an update by using the
+update button which is available on the **Mirroring repositories** section of the **Repository Settings** page.
+
+![Repository mirroring force update user interface](img/repository_mirroring_force_update.png)
+
+## Bidirectional mirroring **[STARTER]**
+
+CAUTION: **Caution:**
+Bidirectional mirroring may cause conflicts.
+
+If you configure a GitLab repository to both pull from, and push to, the same remote source, there
+is no guarantee that either repository will update correctly. If you set up a repository for
+bidirectional mirroring, you should prepare for the likely conflicts by deciding who will resolve
+them and how they will be resolved.
+
+Rewriting any mirrored commit on either remote will cause conflicts and mirroring to fail. This can
+be prevented by:
+
+- [Pulling only protected branches](#pull-only-protected-branches).
+- [Pushing only protected branches](#push-only-protected-branches).
+
+You should [protect the branches](../user/project/protected_branches.md) you wish to mirror on both
+remotes to prevent conflicts caused by rewriting history.
+
+Bidirectional mirroring also creates a race condition where commits made close together to the same
+branch causes conflicts. The race condition can be mitigated by reducing the mirroring delay by using
+a [Push event webhook](../user/project/integrations/webhooks.md#push-events) to trigger an immediate
+pull to GitLab. Push mirroring from GitLab is rate limited to once per minute when only push mirroring
+protected branches.
+
+### Preventing conflicts using a `pre-receive` hook
+
+> **Warning:** The solution proposed will negatively impact the performance of
+> Git push operations because they will be proxied to the upstream Git
+> repository.
+
+A server-side `pre-receive` hook can be used to prevent the race condition
+described above by only accepting the push after first pushing the commit to
+the upstream Git repository. In this configuration one Git repository acts as
+the authoritative upstream, and the other as downstream. The `pre-receive` hook
+will be installed on the downstream repository.
+
+Read about [configuring custom Git hooks](../administration/custom_hooks.md) on the GitLab server.
+
+A sample `pre-receive` hook is provided below.
+
+```bash
+#!/usr/bin/env bash
+
+# --- Assume only one push mirror target
+# Push mirroring remotes are named `remote_mirror_<id>`, this finds the first remote and uses that.
+TARGET_REPO=$(git remote | grep -m 1 remote_mirror)
+
+proxy_push()
+{
+ # --- Arguments
+ OLDREV=$(git rev-parse $1)
+ NEWREV=$(git rev-parse $2)
+ REFNAME="$3"
+
+ # --- Pattern of branches to proxy pushes
+ whitelisted=$(expr "$branch" : "\(master\)")
+
+ case "$refname" in
+ refs/heads/*)
+ branch=$(expr "$refname" : "refs/heads/\(.*\)")
+
+ if [ "$whitelisted" = "$branch" ]; then
+ error="$(git push --quiet $TARGET_REPO $NEWREV:$REFNAME 2>&1)"
+ fail=$?
+
+ if [ "$fail" != "0" ]; then
+ echo >&2 ""
+ echo >&2 " Error: updates were rejected by upstream server"
+ echo >&2 " This is usually caused by another repository pushing changes"
+ echo >&2 " to the same ref. You may want to first integrate remote changes"
+ echo >&2 ""
+ return
+ fi
+ fi
+ ;;
+ esac
+}
+
+# Allow dual mode: run from the command line just like the update hook, or
+# if no arguments are given then run as a hook script
+if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
+ # Output to the terminal in command line mode - if someone wanted to
+ # resend an email; they could redirect the output to sendmail
+ # themselves
+ PAGER= proxy_push $2 $3 $1
+else
+ # Push is proxied upstream one ref at a time. Because of this it is possible
+ # for some refs to succeed, and others to fail. This will result in a failed
+ # push.
+ while read oldrev newrev refname
+ do
+ proxy_push $oldrev $newrev $refname
+ done
+fi
+```
- ![update now](repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png)
+### Mirroring with Perforce Helix via Git Fusion **[STARTER]**
-## Forcing an update
+CAUTION: **Warning:**
+Bidirectional mirroring should not be used as a permanent configuration. Refer to
+[Migrating from Perforce Helix](../user/project/import/perforce.md) for alternative migration approaches.
-While mirrors are scheduled to update automatically, you can always force an update
-by using the **Update now** button which is exposed in various places:
+[Git Fusion](https://www.perforce.com/video-tutorials/git-fusion-overview) provides a Git interface
+to [Perforce Helix](https://www.perforce.com/products) which can be used by GitLab to bidirectionally
+mirror projects with GitLab. This may be useful in some situations when migrating from Perforce Helix
+to GitLab where overlapping Perforce Helix workspaces cannot be migrated simultaneously to GitLab.
-- in the commits page
-- in the branches page
-- in the tags page
-- in the **Mirror repository** settings page
+If using mirroring with Perforce Helix, you should only mirror protected branches. Perforce Helix
+will reject any pushes that rewrite history. Only the fewest number of branches should be mirrored
+due to the performance limitations of Git Fusion.
-## Bidirectional mirroring
+When configuring mirroring with Perforce Helix via Git Fusion, the following Git Fusion
+settings are recommended:
-CAUTION: **Warning:**
-There is no bidirectional support without conflicts. If you
-configure a repository to pull and push to a second remote, there is no
-guarantee that it will update correctly on both remotes. If you configure
-a repository for bidirectional mirroring, you should consider when conflicts
-occur who and how they will be resolved.
-
-Rewriting any mirrored commit on either remote will cause conflicts and
-mirroring to fail. This can be prevented by [only pulling protected branches](
-#pull-only-protected-branches) and [only pushing protected branches](
-#push-only-protected-branches). You should protect the branches you wish to
-mirror on both remotes to prevent conflicts caused by rewriting history.
-
-Bidirectional mirroring also creates a race condition where commits to the same
-branch in close proximity will cause conflicts. The race condition can be
-mitigated by reducing the mirroring delay by using a Push event webhook to
-trigger an immediate pull to GitLab. Push mirroring from GitLab is rate limited
-to once per minute when only push mirroring protected branches.
-
-It may be possible to implement a locking mechanism using the server-side
-`pre-receive` hook to prevent the race condition. Read about [configuring
-custom Git hooks][hooks] on the GitLab server.
-
-### Mirroring with Perforce via GitFusion
+- `change-pusher` should be disabled. Otherwise, every commit will be rewritten as being committed
+ by the mirroring account, rather than being mapped to existing Perforce Helix users or the `unknown_git` user.
+- `unknown_git` user will be used as the commit author if the GitLab user does not exist in
+ Perforce Helix.
-CAUTION: **Warning:**
-Bidirectional mirroring should not be used as a permanent
-configuration. There is no bidirectional mirroring without conflicts.
-Refer to [Migrating from Perforce Helix][perforce] for alternative migration
-approaches.
-
-GitFusion provides a Git interface to Perforce which can be used by GitLab to
-bidirectionally mirror projects with GitLab. This may be useful in some
-situations when migrating from Perforce to GitLab where overlapping Perforce
-workspaces cannot be migrated simultaneously to GitLab.
-
-If using mirroring with Perforce you should only mirror protected branches.
-Perforce will reject any pushes that rewrite history. It is recommended that
-only the fewest number of branches are mirrored due to the performance
-limitations of GitFusion.
-
-[ee-51]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51
-[ee-2551]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551
-[ee-3117]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117
-[ee-3326]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326
-[ee-3350]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350
-[ee-3453]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453
-[ee-4559]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4559
-[ce-18715]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715
-[perms]: ../user/permissions.md
-[hooks]: ../administration/custom_hooks.md
-[deploy-key]: ../ssh/README.md#deploy-keys
-[webhook]: ../user/project/integrations/webhooks.md#push-events
-[pull-api]: ../api/projects.md#start-the-pull-mirroring-process-for-a-project
-[perforce]: ../user/project/import/perforce.md
+Read about [Git Fusion settings on Perforce.com](https://www.perforce.com/perforce/doc.current/manuals/git-fusion/Content/Git-Fusion/section_zdp_zz1_3l.html).
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png b/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png
deleted file mode 100644
index 2377a4a6516..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png b/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png
deleted file mode 100644
index 45c9bce0889..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch_push.png b/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch_push.png
deleted file mode 100644
index 786bd23eee6..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch_push.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_github_edit_personal_access_token.png b/doc/workflow/repository_mirroring/repository_mirroring_github_edit_personal_access_token.png
deleted file mode 100644
index 139de42d8db..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_github_edit_personal_access_token.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.png b/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.png
deleted file mode 100644
index ccbc1d92329..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png b/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png
deleted file mode 100644
index b16b3d2828e..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png b/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png
deleted file mode 100644
index d8af5ce129e..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png b/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png
deleted file mode 100644
index a10102e97ac..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_new_project.png b/doc/workflow/repository_mirroring/repository_mirroring_new_project.png
deleted file mode 100644
index 43bf304838f..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_new_project.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png
deleted file mode 100644
index 1f1b3e1d5fb..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png
deleted file mode 100644
index b8dfddb3d02..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png
deleted file mode 100644
index 8f1de1d3003..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_push_settings.png b/doc/workflow/repository_mirroring/repository_mirroring_push_settings.png
deleted file mode 100644
index f8199aa7c0f..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_push_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png b/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png
deleted file mode 100644
index 930d10a0822..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png b/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png
deleted file mode 100644
index adc1eedac44..00000000000
--- a/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png
+++ /dev/null
Binary files differ
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
index f29cd7fc003..92717e04543 100644
--- a/lib/api/applications.rb
+++ b/lib/api/applications.rb
@@ -24,6 +24,22 @@ module API
render_validation_error! application
end
end
+
+ desc 'Get applications' do
+ success Entities::Application
+ end
+ get do
+ applications = ApplicationsFinder.new.execute
+ present applications, with: Entities::Application
+ end
+
+ desc 'Delete an application'
+ delete ':id' do
+ application = ApplicationsFinder.new(params).execute
+ application.destroy
+
+ status 204
+ end
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 5a4b85f98cf..18c30723d73 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -260,7 +260,7 @@ module API
super(projects_relation).preload(:group)
.preload(project_group_links: :group,
fork_network: :root_project,
- forked_project_link: :forked_from_project,
+ fork_network_member: :forked_from_project,
forked_from_project: [:route, :forks, :tags, namespace: :route])
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -1412,7 +1412,9 @@ module API
end
class Application < Grape::Entity
+ expose :id
expose :uid, as: :application_id
+ expose :name, as: :application_name
expose :redirect_uri, as: :callback_url
end
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 6f2422af13a..1331248699f 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -20,7 +20,7 @@ module API
def gate_targets(params)
targets = []
targets << Feature.group(params[:feature_group]) if params[:feature_group]
- targets << User.find_by_username(params[:user]) if params[:user]
+ targets << UserFinder.new(params[:user]).find_by_username if params[:user]
targets
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index a7ba8066233..60bf977f0e4 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -96,15 +96,9 @@ module API
LabelsFinder.new(current_user, search_params).execute
end
- # rubocop: disable CodeReuse/ActiveRecord
def find_user(id)
- if id =~ /^\d+$/
- User.find_by(id: id)
- else
- User.find_by(username: id)
- end
+ UserFinder.new(id).find_by_id_or_username
end
- # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def find_project(id)
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 6a264c4cc6d..4dd6b19e353 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -40,7 +40,7 @@ module API
elsif params[:user_id]
User.find_by(id: params[:user_id])
elsif params[:username]
- User.find_by_username(params[:username])
+ UserFinder.new(params[:username]).find_by_username
end
protocol = params[:protocol]
@@ -154,7 +154,7 @@ module API
elsif params[:user_id]
user = User.find_by(id: params[:user_id])
elsif params[:username]
- user = User.find_by(username: params[:username])
+ user = UserFinder.new(params[:username]).find_by_username
end
present user, with: Entities::UserSafe
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index fa992b9a440..697555c9605 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -151,7 +151,7 @@ module API
present build, with: Entities::Job
end
- desc 'Trigger a actionable job (manual, scheduled, etc)' do
+ desc 'Trigger a actionable job (manual, delayed, etc)' do
success Entities::Job
detail 'This feature was added in GitLab 8.11'
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 501c5cf1df3..47382b09207 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -155,7 +155,6 @@ module API
requires :username, type: String, desc: 'The username of the user'
use :optional_attributes
end
- # rubocop: disable CodeReuse/ActiveRecord
post do
authenticated_as_admin!
@@ -166,17 +165,16 @@ module API
present user, with: Entities::UserPublic, current_user: current_user
else
conflict!('Email has already been taken') if User
- .where(email: user.email)
- .count > 0
+ .by_any_email(user.email.downcase)
+ .any?
conflict!('Username has already been taken') if User
- .where(username: user.username)
- .count > 0
+ .by_username(user.username)
+ .any?
render_validation_error!(user)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Update a user. Available only for admins.' do
success Entities::UserPublic
@@ -198,11 +196,11 @@ module API
not_found!('User') unless user
conflict!('Email has already been taken') if params[:email] &&
- User.where(email: params[:email])
+ User.by_any_email(params[:email].downcase)
.where.not(id: user.id).count > 0
conflict!('Username has already been taken') if params[:username] &&
- User.where(username: params[:username])
+ User.by_username(params[:username])
.where.not(id: user.id).count > 0
user_params = declared_params(include_missing: false)
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index afdc6f383c1..a0434a66ef1 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -164,7 +164,7 @@ module Backup
def tar_version
tar_version, _ = Gitlab::Popen.popen(%w(tar --version))
- tar_version.force_encoding('locale').split("\n").first
+ tar_version.dup.force_encoding('locale').split("\n").first
end
def skipped?(item)
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index b170145f013..ec090aea784 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Gitlab::Access module
#
# Define allowed roles that can be used
diff --git a/lib/gitlab/action_rate_limiter.rb b/lib/gitlab/action_rate_limiter.rb
index 4cd3bdefda3..c442211e073 100644
--- a/lib/gitlab/action_rate_limiter.rb
+++ b/lib/gitlab/action_rate_limiter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# This class implements a simple rate limiter that can be used to throttle
# certain actions. Unlike Rack Attack and Rack::Throttle, which operate at
diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb
index 45c2b01dd8f..4518c8a862c 100644
--- a/lib/gitlab/allowable.rb
+++ b/lib/gitlab/allowable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Allowable
def can?(*args)
diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb
index dddcb2538f9..5edec8b3efe 100644
--- a/lib/gitlab/app_logger.rb
+++ b/lib/gitlab/app_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class AppLogger < Gitlab::Logger
def self.file_name_noext
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index 62c41801d75..df8f0470063 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'asciidoctor'
require 'asciidoctor/converter/html5'
require "asciidoctor-plantuml"
diff --git a/lib/gitlab/audit_json_logger.rb b/lib/gitlab/audit_json_logger.rb
new file mode 100644
index 00000000000..12e0645f3e4
--- /dev/null
+++ b/lib/gitlab/audit_json_logger.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class AuditJsonLogger < Gitlab::JsonLogger
+ def self.file_name_noext
+ 'audit_json'
+ end
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index a36d551d1d7..d2029a141e7 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
MissingPersonalAccessTokenError = Class.new(StandardError)
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index 36c85dec544..d72befce571 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module BackgroundMigration
def self.queue
diff --git a/lib/gitlab/base_doorkeeper_controller.rb b/lib/gitlab/base_doorkeeper_controller.rb
index e4227af25d2..b78993aba30 100644
--- a/lib/gitlab/base_doorkeeper_controller.rb
+++ b/lib/gitlab/base_doorkeeper_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This is a base controller for doorkeeper.
# It adds the `can?` helper used in the views.
module Gitlab
diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb
index 169aac79854..0d79594363e 100644
--- a/lib/gitlab/blame.rb
+++ b/lib/gitlab/blame.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class Blame
attr_accessor :blob, :commit
diff --git a/lib/gitlab/blob_helper.rb b/lib/gitlab/blob_helper.rb
index 9b3b383b0c8..488c1d85387 100644
--- a/lib/gitlab/blob_helper.rb
+++ b/lib/gitlab/blob_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This has been extracted from https://github.com/github/linguist/blob/master/lib/linguist/blob_helper.rb
module Gitlab
module BlobHelper
diff --git a/lib/gitlab/build_access.rb b/lib/gitlab/build_access.rb
index 08a8f846ca5..37e79413541 100644
--- a/lib/gitlab/build_access.rb
+++ b/lib/gitlab/build_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class BuildAccess < UserAccess
attr_accessor :user, :project
diff --git a/lib/gitlab/changes_list.rb b/lib/gitlab/changes_list.rb
index 9c9e6668e6f..fb75a78a978 100644
--- a/lib/gitlab/changes_list.rb
+++ b/lib/gitlab/changes_list.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ChangesList
include Enumerable
diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb
index e63e5437331..8b3c5dc9e8b 100644
--- a/lib/gitlab/chat_name_token.rb
+++ b/lib/gitlab/chat_name_token.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'json'
module Gitlab
diff --git a/lib/gitlab/ci/build/artifacts/adapters/gzip_stream.rb b/lib/gitlab/ci/build/artifacts/adapters/gzip_stream.rb
new file mode 100644
index 00000000000..ee3647f24fd
--- /dev/null
+++ b/lib/gitlab/ci/build/artifacts/adapters/gzip_stream.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module Ci
+ module Build
+ module Artifacts
+ module Adapters
+ class GzipStream
+ attr_reader :stream
+
+ InvalidStreamError = Class.new(StandardError)
+
+ def initialize(stream)
+ raise InvalidStreamError, "Stream is required" unless stream
+
+ @stream = stream
+ end
+
+ def each_blob
+ stream.seek(0)
+
+ until stream.eof?
+ gzip(stream) do |gz|
+ yield gz.read, gz.orig_name
+ unused = gz.unused&.length.to_i
+ # pos has already reached to EOF at the moment
+ # We rewind the pos to the top of unused files
+ # to read next gzip stream, to support multistream archives
+ # https://golang.org/src/compress/gzip/gunzip.go#L117
+ stream.seek(-unused, IO::SEEK_CUR)
+ end
+ end
+ end
+
+ private
+
+ def gzip(stream, &block)
+ gz = Zlib::GzipReader.new(stream)
+ yield(gz)
+ rescue Zlib::Error => e
+ raise InvalidStreamError, e.message
+ ensure
+ gz&.finish
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/artifacts/adapters/raw_stream.rb b/lib/gitlab/ci/build/artifacts/adapters/raw_stream.rb
new file mode 100644
index 00000000000..fa6842cf36a
--- /dev/null
+++ b/lib/gitlab/ci/build/artifacts/adapters/raw_stream.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module Ci
+ module Build
+ module Artifacts
+ module Adapters
+ class RawStream
+ attr_reader :stream
+
+ InvalidStreamError = Class.new(StandardError)
+
+ def initialize(stream)
+ raise InvalidStreamError, "Stream is required" unless stream
+
+ @stream = stream
+ end
+
+ def each_blob
+ stream.seek(0)
+
+ yield(stream.read, 'raw') unless stream.eof?
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/artifacts/gzip_file_adapter.rb b/lib/gitlab/ci/build/artifacts/gzip_file_adapter.rb
deleted file mode 100644
index 65f65cdce08..00000000000
--- a/lib/gitlab/ci/build/artifacts/gzip_file_adapter.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-module Gitlab
- module Ci
- module Build
- module Artifacts
- class GzipFileAdapter
- attr_reader :stream
-
- InvalidStreamError = Class.new(StandardError)
-
- def initialize(stream)
- raise InvalidStreamError, "Stream is required" unless stream
-
- @stream = stream
- end
-
- def each_blob
- stream.seek(0)
-
- until stream.eof?
- gzip(stream) do |gz|
- yield gz.read, gz.orig_name
- unused = gz.unused&.length.to_i
- # pos has already reached to EOF at the moment
- # We rewind the pos to the top of unused files
- # to read next gzip stream, to support multistream archives
- # https://golang.org/src/compress/gzip/gunzip.go#L117
- stream.seek(-unused, IO::SEEK_CUR)
- end
- end
- end
-
- private
-
- def gzip(stream, &block)
- gz = Zlib::GzipReader.new(stream)
- yield(gz)
- rescue Zlib::Error => e
- raise InvalidStreamError, e.message
- ensure
- gz&.finish
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index 375d8bc1ff5..551d4f4473e 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -59,9 +59,12 @@ module Gitlab
until gz.eof?
begin
- path = read_string(gz).force_encoding('UTF-8')
- meta = read_string(gz).force_encoding('UTF-8')
+ path = read_string(gz)&.force_encoding('UTF-8')
+ meta = read_string(gz)&.force_encoding('UTF-8')
+ # We might hit an EOF while reading either value, so we should
+ # abort if we don't get any data.
+ next unless path && meta
next unless path.valid_encoding? && meta.valid_encoding?
next unless path =~ match_pattern
next if path =~ INVALID_PATH_PATTERN
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index fe98d25af29..fedaf18ef30 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -13,10 +13,10 @@ module Gitlab
@global = Entry::Global.new(@config)
@global.compose!
- rescue Loader::FormatError, Extendable::ExtensionError => e
+ rescue Loader::FormatError,
+ Extendable::ExtensionError,
+ External::Processor::IncludeError => e
raise Config::ConfigError, e.message
- rescue ::Gitlab::Ci::External::Processor::FileError => e
- raise ::Gitlab::Ci::YamlProcessor::ValidationError, e.message
end
def valid?
@@ -81,7 +81,7 @@ module Gitlab
def process_external_files(config, project, opts)
sha = opts.fetch(:sha) { project.repository.root_ref_sha }
- ::Gitlab::Ci::External::Processor.new(config, project, sha).perform
+ Config::External::Processor.new(config, project, sha).perform
end
end
end
diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb
index 98f12c226b3..3ac2a6fa777 100644
--- a/lib/gitlab/ci/config/entry/reports.rb
+++ b/lib/gitlab/ci/config/entry/reports.rb
@@ -11,7 +11,7 @@ module Gitlab
include Validatable
include Attributable
- ALLOWED_KEYS = %i[junit codequality sast dependency_scanning container_scanning dast].freeze
+ ALLOWED_KEYS = %i[junit codequality sast dependency_scanning container_scanning dast performance license_management].freeze
attributes ALLOWED_KEYS
@@ -26,6 +26,8 @@ module Gitlab
validates :dependency_scanning, array_of_strings_or_string: true
validates :container_scanning, array_of_strings_or_string: true
validates :dast, array_of_strings_or_string: true
+ validates :performance, array_of_strings_or_string: true
+ validates :license_management, array_of_strings_or_string: true
end
end
diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb
new file mode 100644
index 00000000000..15ca47ef60e
--- /dev/null
+++ b/lib/gitlab/ci/config/external/file/base.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ module File
+ class Base
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :location, :opts, :errors
+
+ YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
+
+ def initialize(location, opts = {})
+ @location = location
+ @opts = opts
+ @errors = []
+
+ validate!
+ end
+
+ def invalid_extension?
+ !::File.basename(location).match(YAML_WHITELIST_EXTENSION)
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def error_message
+ errors.first
+ end
+
+ def content
+ raise NotImplementedError, 'subclass must implement fetching raw content'
+ end
+
+ def to_hash
+ @hash ||= Ci::Config::Loader.new(content).load!
+ rescue Ci::Config::Loader::FormatError
+ nil
+ end
+
+ protected
+
+ def validate!
+ validate_location!
+ validate_content! if errors.none?
+ validate_hash! if errors.none?
+ end
+
+ def validate_location!
+ if invalid_extension?
+ errors.push("Included file `#{location}` does not have YAML extension!")
+ end
+ end
+
+ def validate_content!
+ if content.blank?
+ errors.push("Included file `#{location}` is empty or does not exist!")
+ end
+ end
+
+ def validate_hash!
+ if to_hash.blank?
+ errors.push("Included file `#{location}` does not have valid YAML syntax!")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/file/local.rb b/lib/gitlab/ci/config/external/file/local.rb
new file mode 100644
index 00000000000..2a256aff65c
--- /dev/null
+++ b/lib/gitlab/ci/config/external/file/local.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ module File
+ class Local < Base
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :project, :sha
+
+ def initialize(location, opts = {})
+ @project = opts.fetch(:project)
+ @sha = opts.fetch(:sha)
+
+ super
+ end
+
+ def content
+ strong_memoize(:content) { fetch_local_content }
+ end
+
+ private
+
+ def validate_content!
+ if content.nil?
+ errors.push("Local file `#{location}` does not exist!")
+ elsif content.blank?
+ errors.push("Local file `#{location}` is empty!")
+ end
+ end
+
+ def fetch_local_content
+ project.repository.blob_data_at(sha, location)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/file/remote.rb b/lib/gitlab/ci/config/external/file/remote.rb
new file mode 100644
index 00000000000..86fa5ad8800
--- /dev/null
+++ b/lib/gitlab/ci/config/external/file/remote.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ module File
+ class Remote < Base
+ include Gitlab::Utils::StrongMemoize
+
+ def content
+ strong_memoize(:content) { fetch_remote_content }
+ end
+
+ private
+
+ def validate_location!
+ super
+
+ unless ::Gitlab::UrlSanitizer.valid?(location)
+ errors.push("Remote file `#{location}` does not have a valid address!")
+ end
+ end
+
+ def fetch_remote_content
+ begin
+ response = Gitlab::HTTP.get(location)
+ rescue SocketError
+ errors.push("Remote file `#{location}` could not be fetched because of a socket error!")
+ rescue Timeout::Error
+ errors.push("Remote file `#{location}` could not be fetched because of a timeout error!")
+ rescue Gitlab::HTTP::Error
+ errors.push("Remote file `#{location}` could not be fetched because of HTTP error!")
+ rescue Gitlab::HTTP::BlockedUrlError => e
+ errors.push("Remote file could not be fetched because #{e}!")
+ end
+
+ if response&.code.to_i >= 400
+ errors.push("Remote file `#{location}` could not be fetched because of HTTP code `#{response.code}` error!")
+ end
+
+ response.to_s if errors.none?
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb
new file mode 100644
index 00000000000..def3563e505
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ def initialize(values, project, sha)
+ @locations = Array(values.fetch(:include, []))
+ @project = project
+ @sha = sha
+ end
+
+ def process
+ locations.map { |location| build_external_file(location) }
+ end
+
+ private
+
+ attr_reader :locations, :project, :sha
+
+ def build_external_file(location)
+ if ::Gitlab::UrlSanitizer.valid?(location)
+ External::File::Remote.new(location)
+ else
+ External::File::Local.new(location, project: project, sha: sha)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb
new file mode 100644
index 00000000000..eae0bdeb644
--- /dev/null
+++ b/lib/gitlab/ci/config/external/processor.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Processor
+ IncludeError = Class.new(StandardError)
+
+ def initialize(values, project, sha)
+ @values = values
+ @external_files = External::Mapper.new(values, project, sha).process
+ @content = {}
+ end
+
+ def perform
+ return @values if @external_files.empty?
+
+ validate_external_files!
+ merge_external_files!
+ append_inline_content!
+ remove_include_keyword!
+ end
+
+ private
+
+ def validate_external_files!
+ @external_files.each do |file|
+ raise IncludeError, file.error_message unless file.valid?
+ end
+ end
+
+ def merge_external_files!
+ @external_files.each do |file|
+ @content.deep_merge!(file.to_hash)
+ end
+ end
+
+ def append_inline_content!
+ @content.deep_merge!(@values)
+ end
+
+ def remove_include_keyword!
+ @content.tap { @content.delete(:include) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/external/file/base.rb b/lib/gitlab/ci/external/file/base.rb
deleted file mode 100644
index f4da07b0b02..00000000000
--- a/lib/gitlab/ci/external/file/base.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- module File
- class Base
- YAML_WHITELIST_EXTENSION = /(yml|yaml)$/i.freeze
-
- def initialize(location, opts = {})
- @location = location
- end
-
- def valid?
- location.match(YAML_WHITELIST_EXTENSION) && content
- end
-
- def content
- raise NotImplementedError, 'content must be implemented and return a string or nil'
- end
-
- def error_message
- raise NotImplementedError, 'error_message must be implemented and return a string'
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/external/file/local.rb b/lib/gitlab/ci/external/file/local.rb
deleted file mode 100644
index 1aa7f687507..00000000000
--- a/lib/gitlab/ci/external/file/local.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- module File
- class Local < Base
- attr_reader :location, :project, :sha
-
- def initialize(location, opts = {})
- super
-
- @project = opts.fetch(:project)
- @sha = opts.fetch(:sha)
- end
-
- def content
- @content ||= fetch_local_content
- end
-
- def error_message
- "Local file '#{location}' is not valid."
- end
-
- private
-
- def fetch_local_content
- project.repository.blob_data_at(sha, location)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/external/file/remote.rb b/lib/gitlab/ci/external/file/remote.rb
deleted file mode 100644
index 59bb3e8999e..00000000000
--- a/lib/gitlab/ci/external/file/remote.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- module File
- class Remote < Base
- include Gitlab::Utils::StrongMemoize
- attr_reader :location
-
- def content
- return @content if defined?(@content)
-
- @content = strong_memoize(:content) do
- begin
- Gitlab::HTTP.get(location)
- rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Gitlab::HTTP::BlockedUrlError
- nil
- end
- end
- end
-
- def error_message
- "Remote file '#{location}' is not valid."
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/external/mapper.rb b/lib/gitlab/ci/external/mapper.rb
deleted file mode 100644
index 58bd6a19acf..00000000000
--- a/lib/gitlab/ci/external/mapper.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- class Mapper
- def initialize(values, project, sha)
- @locations = Array(values.fetch(:include, []))
- @project = project
- @sha = sha
- end
-
- def process
- locations.map { |location| build_external_file(location) }
- end
-
- private
-
- attr_reader :locations, :project, :sha
-
- def build_external_file(location)
- if ::Gitlab::UrlSanitizer.valid?(location)
- Gitlab::Ci::External::File::Remote.new(location)
- else
- options = { project: project, sha: sha }
- Gitlab::Ci::External::File::Local.new(location, options)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/external/processor.rb b/lib/gitlab/ci/external/processor.rb
deleted file mode 100644
index 76cf3ce89f9..00000000000
--- a/lib/gitlab/ci/external/processor.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- class Processor
- FileError = Class.new(StandardError)
-
- def initialize(values, project, sha)
- @values = values
- @external_files = Gitlab::Ci::External::Mapper.new(values, project, sha).process
- @content = {}
- end
-
- def perform
- return values if external_files.empty?
-
- external_files.each do |external_file|
- validate_external_file(external_file)
- @content.deep_merge!(content_of(external_file))
- end
-
- append_inline_content
- remove_include_keyword
- end
-
- private
-
- attr_reader :values, :external_files, :content
-
- def validate_external_file(external_file)
- unless external_file.valid?
- raise FileError, external_file.error_message
- end
- end
-
- def content_of(external_file)
- Gitlab::Ci::Config::Loader.new(external_file.content).load!
- end
-
- def append_inline_content
- @content.deep_merge!(@values)
- end
-
- def remove_include_keyword
- content.delete(:include)
- content
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/status/build/scheduled.rb b/lib/gitlab/ci/status/build/scheduled.rb
index eebb3f761c5..62ad9083616 100644
--- a/lib/gitlab/ci/status/build/scheduled.rb
+++ b/lib/gitlab/ci/status/build/scheduled.rb
@@ -7,7 +7,7 @@ module Gitlab
{
image: 'illustrations/illustrations_scheduled-job_countdown.svg',
size: 'svg-394',
- title: _("This is a scheduled to run in ") + " #{execute_in}",
+ title: _("This is a delayed to run in ") + " #{execute_in}",
content: _("This job will automatically run after it's timer finishes. " \
"Often they are used for incremental roll-out deploys " \
"to production environments. When unscheduled it converts " \
@@ -16,7 +16,7 @@ module Gitlab
end
def status_tooltip
- "scheduled manual action (#{execute_in})"
+ "delayed manual action (#{execute_in})"
end
def self.matches?(build, user)
@@ -29,7 +29,7 @@ module Gitlab
def execute_in
remaining_seconds = [0, subject.scheduled_at - Time.now].max
- duration_in_numbers(remaining_seconds, true)
+ duration_in_numbers(remaining_seconds)
end
end
end
diff --git a/lib/gitlab/ci/status/pipeline/scheduled.rb b/lib/gitlab/ci/status/pipeline/delayed.rb
index 9ec6994bd2f..12736861c89 100644
--- a/lib/gitlab/ci/status/pipeline/scheduled.rb
+++ b/lib/gitlab/ci/status/pipeline/delayed.rb
@@ -2,9 +2,9 @@ module Gitlab
module Ci
module Status
module Pipeline
- class Scheduled < Status::Extended
+ class Delayed < Status::Extended
def text
- s_('CiStatusText|scheduled')
+ s_('CiStatusText|delayed')
end
def label
diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb
index 00d8f01cbdc..0adf83fa197 100644
--- a/lib/gitlab/ci/status/pipeline/factory.rb
+++ b/lib/gitlab/ci/status/pipeline/factory.rb
@@ -5,7 +5,7 @@ module Gitlab
class Factory < Status::Factory
def self.extended_statuses
[[Status::SuccessWarning,
- Status::Pipeline::Scheduled,
+ Status::Pipeline::Delayed,
Status::Pipeline::Blocked]]
end
diff --git a/lib/gitlab/ci/status/scheduled.rb b/lib/gitlab/ci/status/scheduled.rb
index 542100e41da..3adcfa36af2 100644
--- a/lib/gitlab/ci/status/scheduled.rb
+++ b/lib/gitlab/ci/status/scheduled.rb
@@ -3,11 +3,11 @@ module Gitlab
module Status
class Scheduled < Status::Core
def text
- s_('CiStatusText|scheduled')
+ s_('CiStatusText|delayed')
end
def label
- s_('CiStatusLabel|scheduled')
+ s_('CiStatusLabel|delayed')
end
def icon
diff --git a/lib/gitlab/ci_access.rb b/lib/gitlab/ci_access.rb
index def1373d8cf..d5d3eb804ae 100644
--- a/lib/gitlab/ci_access.rb
+++ b/lib/gitlab/ci_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# For backwards compatibility, generic CI (which is a build without a user) is
# allowed to :build_download_code without any other checks.
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index 7e7aaeeaa17..4ba921569ad 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ClosingIssueExtractor
ISSUE_CLOSING_REGEX = begin
diff --git a/lib/gitlab/color_schemes.rb b/lib/gitlab/color_schemes.rb
index 9c4664df903..a5e4065cf09 100644
--- a/lib/gitlab/color_schemes.rb
+++ b/lib/gitlab/color_schemes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Module containing GitLab's syntax color scheme definitions and helper
# methods for accessing them.
diff --git a/lib/gitlab/config_helper.rb b/lib/gitlab/config_helper.rb
index 41880069e4c..b7aa03384b7 100644
--- a/lib/gitlab/config_helper.rb
+++ b/lib/gitlab/config_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab::ConfigHelper
def gitlab_config_features
Gitlab.config.gitlab.default_projects_features
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 1ffc2639237..c819bffdfd6 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ContributionsCalendar
attr_reader :contributor
diff --git a/lib/gitlab/contributor.rb b/lib/gitlab/contributor.rb
index c41e92b620f..d74d5a86aa0 100644
--- a/lib/gitlab/contributor.rb
+++ b/lib/gitlab/contributor.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class Contributor
attr_accessor :email, :name, :commits, :additions, :deletions
diff --git a/lib/gitlab/cross_project_access.rb b/lib/gitlab/cross_project_access.rb
index 6eaed51b64c..4ddc7e02d1b 100644
--- a/lib/gitlab/cross_project_access.rb
+++ b/lib/gitlab/cross_project_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class CrossProjectAccess
class << self
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index de7c959e706..477f9101e98 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module CurrentSettings
class << self
diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb
index bd14c7eece3..6d5fc4219fb 100644
--- a/lib/gitlab/daemon.rb
+++ b/lib/gitlab/daemon.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class Daemon
def self.initialize_instance(*args)
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 42f9605f5ac..68ed53cf64a 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Database
# The max value of INTEGER type is the same between MySQL and PostgreSQL:
@@ -99,11 +101,11 @@ module Gitlab
order = "#{field} #{direction}"
if postgresql?
- order << ' NULLS LAST'
+ order = "#{order} NULLS LAST"
else
# `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
# columns. In the (default) ascending order, `0` comes first.
- order.prepend("#{field} IS NULL, ") if direction == 'ASC'
+ order = "#{field} IS NULL, #{order}" if direction == 'ASC'
end
order
@@ -113,11 +115,11 @@ module Gitlab
order = "#{field} #{direction}"
if postgresql?
- order << ' NULLS FIRST'
+ order = "#{order} NULLS FIRST"
else
# `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
# columns. In the (default) ascending order, `0` comes first.
- order.prepend("#{field} IS NULL, ") if direction == 'DESC'
+ order = "#{field} IS NULL, #{order}" if direction == 'DESC'
end
order
@@ -184,7 +186,7 @@ module Gitlab
EOF
if return_ids
- sql << 'RETURNING id'
+ sql = "#{sql}RETURNING id"
end
result = connection.execute(sql)
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 30541ee3553..a17f27a3147 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -937,7 +937,7 @@ database (#{dbname}) using a super user and running:
For MySQL you instead need to run:
- GRANT ALL PRIVILEGES ON *.* TO #{user}@'%'
+ GRANT ALL PRIVILEGES ON #{dbname}.* TO #{user}@'%'
Both queries will grant the user super user permissions, ensuring you don't run
into similar problems in the future (e.g. when new tables are created).
diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index 3192bf6f667..c63d9e5bb71 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module DependencyLinker
LINKERS = [
diff --git a/lib/gitlab/downtime_check.rb b/lib/gitlab/downtime_check.rb
index 941244694e2..31bb6810391 100644
--- a/lib/gitlab/downtime_check.rb
+++ b/lib/gitlab/downtime_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Checks if a set of migrations requires downtime or not.
class DowntimeCheck
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index ee604e66154..5d9ecd651a0 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop: disable Rails/Output
module Gitlab
# Checks if a set of migrations requires downtime or not.
@@ -284,7 +286,7 @@ module Gitlab
end
def patch_name_from_branch(branch_name)
- branch_name.parameterize << '.patch'
+ "#{branch_name.parameterize}.patch"
end
def patch_url
@@ -432,9 +434,11 @@ module Gitlab
end
def conflicting_files_msg
- failed_files.reduce("The conflicts detected were as follows:\n") do |memo, file|
- memo << "\n - #{file}"
- end
+ header = "The conflicts detected were as follows:\n"
+ separator = "\n - "
+ failed_items = failed_files.join(separator)
+
+ "#{header}#{separator}#{failed_items}"
end
end
end
diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb
index 89cf659bce4..ce1dfb0753c 100644
--- a/lib/gitlab/emoji.rb
+++ b/lib/gitlab/emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Emoji
extend self
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index 0f336fbaa10..a4a154c80f7 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module EncodingHelper
extend self
diff --git a/lib/gitlab/environment.rb b/lib/gitlab/environment.rb
index 5e0dd6e7859..b1a9603d3a5 100644
--- a/lib/gitlab/environment.rb
+++ b/lib/gitlab/environment.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Environment
def self.hostname
diff --git a/lib/gitlab/environment_logger.rb b/lib/gitlab/environment_logger.rb
index 407cc572656..862a516ca71 100644
--- a/lib/gitlab/environment_logger.rb
+++ b/lib/gitlab/environment_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class EnvironmentLogger < Gitlab::Logger
def self.file_name_noext
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index 12b5e240962..d466d2a514c 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'securerandom'
module Gitlab
diff --git a/lib/gitlab/exclusive_lease_helpers.rb b/lib/gitlab/exclusive_lease_helpers.rb
index e998548cff9..4aaf2474763 100644
--- a/lib/gitlab/exclusive_lease_helpers.rb
+++ b/lib/gitlab/exclusive_lease_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# This module provides helper methods which are intregrated with GitLab::ExclusiveLease
module ExclusiveLeaseHelpers
diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb
index 2c827265d8c..db1aeeea8d3 100644
--- a/lib/gitlab/fake_application_settings.rb
+++ b/lib/gitlab/fake_application_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class extends an OpenStruct object by adding predicate methods to mimic
# ActiveRecord access. We rely on the initial values being true or false to
# determine whether to define a predicate method because for a newly-added
diff --git a/lib/gitlab/favicon.rb b/lib/gitlab/favicon.rb
index 050a1ad3a0b..1ae2f9dfd93 100644
--- a/lib/gitlab/favicon.rb
+++ b/lib/gitlab/favicon.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class Favicon
class << self
diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb
index da62ed2fb16..4d89ee5a669 100644
--- a/lib/gitlab/file_detector.rb
+++ b/lib/gitlab/file_detector.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'set'
module Gitlab
diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb
index af8270c8db8..b4db3f93c9c 100644
--- a/lib/gitlab/file_finder.rb
+++ b/lib/gitlab/file_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class finds files in a repository by name and content
# the result is joined and sorted by file name
module Gitlab
diff --git a/lib/gitlab/file_markdown_link_builder.rb b/lib/gitlab/file_markdown_link_builder.rb
index 5386656efe7..180140e7da2 100644
--- a/lib/gitlab/file_markdown_link_builder.rb
+++ b/lib/gitlab/file_markdown_link_builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Builds the markdown link of a file
# It needs the methods filename and secure_url (final destination url) to be defined.
module Gitlab
@@ -8,7 +10,7 @@ module Gitlab
return unless name = markdown_name
markdown = "[#{name.gsub(']', '\\]')}](#{secure_url})"
- markdown.prepend("!") if image_or_video? || dangerous?
+ markdown = "!#{markdown}" if image_or_video? || dangerous?
markdown
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 2913a3e416d..c4aac228b2f 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_dependency 'gitlab/encoding_helper'
module Gitlab
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 240a0d7d1b8..827c04ae035 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Check a user's access to perform a git action. All public methods in this
# class return an instance of `GitlabAccessStatus`
module Gitlab
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index a5b3902ebf4..3f24001e4ee 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class GitAccessWiki < GitAccess
ERROR_MESSAGES = {
diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb
index 9e02ccc0f44..dac4ddd320f 100644
--- a/lib/gitlab/git_logger.rb
+++ b/lib/gitlab/git_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class GitLogger < Gitlab::Logger
def self.file_name_noext
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index 40636fb204e..a90b69ff42b 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Gitaly note: JV: does not need to be migrated, works without a repo.
module Gitlab
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 4ec87f6a3e7..d99a9f15371 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'base64'
require 'gitaly'
@@ -23,7 +25,7 @@ module Gitlab
stacks = most_invoked_stack.join('\n') if most_invoked_stack
msg = "GitalyClient##{call_site} called #{invocation_count} times from single request. Potential n+1?"
- msg << "\nThe following call site called into Gitaly #{max_call_stack} times:\n#{stacks}\n" if stacks
+ msg = "#{msg}\nThe following call site called into Gitaly #{max_call_stack} times:\n#{stacks}\n" if stacks
super(msg)
end
diff --git a/lib/gitlab/github_import.rb b/lib/gitlab/github_import.rb
index d40b06f969f..14a6d6443ec 100644
--- a/lib/gitlab/github_import.rb
+++ b/lib/gitlab/github_import.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module GithubImport
def self.refmap
diff --git a/lib/gitlab/gl_id.rb b/lib/gitlab/gl_id.rb
index a53d156b41f..1ed842c2264 100644
--- a/lib/gitlab/gl_id.rb
+++ b/lib/gitlab/gl_id.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module GlId
def self.gl_id(user)
diff --git a/lib/gitlab/gl_repository.rb b/lib/gitlab/gl_repository.rb
index b54e45de4fe..435b74806e7 100644
--- a/lib/gitlab/gl_repository.rb
+++ b/lib/gitlab/gl_repository.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module GlRepository
def self.gl_repository(project, is_wiki)
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index c1726659a90..860c39feb64 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop:disable Metrics/AbcSize
module Gitlab
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index 94c15739231..0c08c0fedaa 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -102,7 +102,7 @@ module Gitlab
if username.start_with?("@")
username = username[1..-1]
- if user = User.find_by(username: username)
+ if user = UserFinder.new(username).find_by_username
assignee_id = user.id
end
end
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
index 8a91e034377..e53c2d00743 100644
--- a/lib/gitlab/gpg.rb
+++ b/lib/gitlab/gpg.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Gpg
extend self
diff --git a/lib/gitlab/graphql.rb b/lib/gitlab/graphql.rb
index 04a89432230..74c04e5380e 100644
--- a/lib/gitlab/graphql.rb
+++ b/lib/gitlab/graphql.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Graphql
StandardGraphqlError = Class.new(StandardError)
diff --git a/lib/gitlab/group_hierarchy.rb b/lib/gitlab/group_hierarchy.rb
index 8fbfa1a86bf..c940ea7305e 100644
--- a/lib/gitlab/group_hierarchy.rb
+++ b/lib/gitlab/group_hierarchy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Retrieving of parent or child groups based on a base ActiveRecord relation.
#
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 0b6cc893db1..83095acc528 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class Highlight
TIMEOUT_BACKGROUND = 30.seconds
diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb
index 9aca3b0fb26..bcd9e2be35f 100644
--- a/lib/gitlab/http.rb
+++ b/lib/gitlab/http.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class is used as a proxy for all outbounding http connection
# coming from callbacks, services and hooks. The direct use of the HTTParty
# is discouraged because it can lead to several security problems, like SSRF
@@ -5,9 +7,16 @@
module Gitlab
class HTTP
BlockedUrlError = Class.new(StandardError)
+ RedirectionTooDeep = Class.new(StandardError)
include HTTParty # rubocop:disable Gitlab/HTTParty
connection_adapter ProxyHTTPConnectionAdapter
+
+ def self.perform_request(http_method, path, options, &block)
+ super
+ rescue HTTParty::RedirectionTooDeep
+ raise RedirectionTooDeep
+ end
end
end
diff --git a/lib/gitlab/http_io.rb b/lib/gitlab/http_io.rb
index ce24817db54..9d7763fc5ac 100644
--- a/lib/gitlab/http_io.rb
+++ b/lib/gitlab/http_io.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
##
# This class is compatible with IO class (https://ruby-doc.org/core-2.3.1/IO.html)
# source: https://gitlab.com/snippets/1685610
@@ -73,8 +75,8 @@ module Gitlab
end
end
- def read(length = nil, outbuf = "")
- out = ""
+ def read(length = nil, outbuf = nil)
+ out = []
length ||= size - tell
@@ -90,17 +92,18 @@ module Gitlab
length -= chunk_data.bytesize
end
+ out = out.join
+
# If outbuf is passed, we put the output into the buffer. This supports IO.copy_stream functionality
if outbuf
- outbuf.slice!(0, outbuf.bytesize)
- outbuf << out
+ outbuf.replace(out)
end
out
end
def readline
- out = ""
+ out = []
until eof?
data = get_chunk
@@ -116,7 +119,7 @@ module Gitlab
end
end
- out
+ out.join
end
def write(data)
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index 7346eab9e76..7e0398f09af 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module I18n
extend self
diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb
index a8b93f1d4b2..28a9fe29465 100644
--- a/lib/gitlab/identifier.rb
+++ b/lib/gitlab/identifier.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Detect user based on identifier like
# key-13 or user-36 or last commit
module Gitlab
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index 53fe2f8e436..f63a5ece71e 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module ImportExport
extend self
diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb
index 4e611e7f16c..d4ba4d1181d 100644
--- a/lib/gitlab/import_formatter.rb
+++ b/lib/gitlab/import_formatter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ImportFormatter
def comment(author, date, body)
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index f7f5c5787f6..f46bb837cf7 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Gitlab::ImportSources module
#
# Define import sources that can be used
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index d323cb9dadf..20fc8226611 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module IncomingEmail
UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze
diff --git a/lib/gitlab/insecure_key_fingerprint.rb b/lib/gitlab/insecure_key_fingerprint.rb
index f85b6e9197f..e4f0e9d2c73 100644
--- a/lib/gitlab/insecure_key_fingerprint.rb
+++ b/lib/gitlab/insecure_key_fingerprint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
#
# Calculates the fingerprint of a given key without using
diff --git a/lib/gitlab/issuable_metadata.rb b/lib/gitlab/issuable_metadata.rb
index 0c9de72329c..351d15605e0 100644
--- a/lib/gitlab/issuable_metadata.rb
+++ b/lib/gitlab/issuable_metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module IssuableMetadata
def issuable_meta_data(issuable_collection, collection_type)
diff --git a/lib/gitlab/issuable_sorter.rb b/lib/gitlab/issuable_sorter.rb
index d392214867a..42bbfb32d0b 100644
--- a/lib/gitlab/issuable_sorter.rb
+++ b/lib/gitlab/issuable_sorter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module IssuableSorter
class << self
diff --git a/lib/gitlab/issuables_count_for_state.rb b/lib/gitlab/issuables_count_for_state.rb
index b5657a36998..659fb1472d2 100644
--- a/lib/gitlab/issuables_count_for_state.rb
+++ b/lib/gitlab/issuables_count_for_state.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Class for counting and caching the number of issuables per state.
class IssuablesCountForState
diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb
index b8ca7f2f55f..17c9cb969df 100644
--- a/lib/gitlab/issues_labels.rb
+++ b/lib/gitlab/issues_labels.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class IssuesLabels
class << self
diff --git a/lib/gitlab/job_waiter.rb b/lib/gitlab/job_waiter.rb
index f7a8eae0be4..e97e961771c 100644
--- a/lib/gitlab/job_waiter.rb
+++ b/lib/gitlab/job_waiter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# JobWaiter can be used to wait for a number of Sidekiq jobs to complete.
#
diff --git a/lib/gitlab/json_logger.rb b/lib/gitlab/json_logger.rb
index 28e258196ca..3bff77731f6 100644
--- a/lib/gitlab/json_logger.rb
+++ b/lib/gitlab/json_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class JsonLogger < ::Gitlab::Logger
def self.file_name_noext
diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb
index 15c5ece2350..3748fd6b5ef 100644
--- a/lib/gitlab/kubernetes.rb
+++ b/lib/gitlab/kubernetes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Helper methods to do with Kubernetes network services & resources
module Kubernetes
diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index 588238de608..e88a15b8acd 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -45,6 +45,13 @@ module Gitlab
:update_cluster_role_binding,
to: :rbac_client
+ # RBAC methods delegates to the apis/rbac.authorization.k8s.io api
+ # group client
+ delegate :create_role_binding,
+ :get_role_binding,
+ :update_role_binding,
+ to: :rbac_client
+
# Deployments resource is currently on the apis/extensions api group
delegate :get_deployments,
to: :extensions_client
diff --git a/lib/gitlab/kubernetes/role_binding.rb b/lib/gitlab/kubernetes/role_binding.rb
new file mode 100644
index 00000000000..4f3ee040bf2
--- /dev/null
+++ b/lib/gitlab/kubernetes/role_binding.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ class RoleBinding
+ attr_reader :role_name, :namespace, :service_account_name
+
+ def initialize(role_name:, namespace:, service_account_name:)
+ @role_name = role_name
+ @namespace = namespace
+ @service_account_name = service_account_name
+ end
+
+ def generate
+ ::Kubeclient::Resource.new.tap do |resource|
+ resource.metadata = metadata
+ resource.roleRef = role_ref
+ resource.subjects = subjects
+ end
+ end
+
+ private
+
+ def metadata
+ { name: "gitlab-#{namespace}", namespace: namespace }
+ end
+
+ def role_ref
+ {
+ apiGroup: 'rbac.authorization.k8s.io',
+ kind: 'Role',
+ name: role_name
+ }
+ end
+
+ def subjects
+ [
+ {
+ kind: 'ServiceAccount',
+ name: service_account_name,
+ namespace: namespace
+ }
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/language_detection.rb b/lib/gitlab/language_detection.rb
index a41435fdb79..7600e60b904 100644
--- a/lib/gitlab/language_detection.rb
+++ b/lib/gitlab/language_detection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class LanguageDetection
MAX_LANGUAGES = 5
diff --git a/lib/gitlab/lazy.rb b/lib/gitlab/lazy.rb
index 99594577141..d7a22aa339e 100644
--- a/lib/gitlab/lazy.rb
+++ b/lib/gitlab/lazy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# A class that can be wrapped around an expensive method call so it's only
# executed when actually needed.
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
index ead5d566871..fa44bd842b2 100644
--- a/lib/gitlab/lfs_token.rb
+++ b/lib/gitlab/lfs_token.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class LfsToken
attr_accessor :actor
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index 3d7c049c17f..128a5dd8936 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class Logger < ::Logger
def self.file_name
diff --git a/lib/gitlab/mail_room.rb b/lib/gitlab/mail_room.rb
index db04356a5e9..78f2d83c1af 100644
--- a/lib/gitlab/mail_room.rb
+++ b/lib/gitlab/mail_room.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'yaml'
require 'json'
require_relative 'redis/queues' unless defined?(Gitlab::Redis::Queues)
diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb
index 49285e35251..142b7d1a472 100644
--- a/lib/gitlab/markup_helper.rb
+++ b/lib/gitlab/markup_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module MarkupHelper
extend self
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 7d63ca5627d..61ed20ad623 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Metrics
include Gitlab::Metrics::InfluxDb
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index 04135dac4ff..ce9d3ec3de4 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -86,7 +86,7 @@ module Gitlab
# Example:
#
# Gitlab::Metrics.measure(:find_by_username_duration) do
- # User.find_by_username(some_username)
+ # UserFinder.new(some_username).find_by_username
# end
#
# name - The name of the field to store the execution time in.
diff --git a/lib/gitlab/multi_collection_paginator.rb b/lib/gitlab/multi_collection_paginator.rb
index fab7a43dd30..5375077d7dc 100644
--- a/lib/gitlab/multi_collection_paginator.rb
+++ b/lib/gitlab/multi_collection_paginator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class MultiCollectionPaginator
attr_reader :first_collection, :second_collection, :per_page
diff --git a/lib/gitlab/namespace_sanitizer.rb b/lib/gitlab/namespace_sanitizer.rb
new file mode 100644
index 00000000000..d755bbbcaf9
--- /dev/null
+++ b/lib/gitlab/namespace_sanitizer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class NamespaceSanitizer
+ def self.sanitize(namespace)
+ namespace.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
+ end
+ end
+end
diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb
index f33ea0880df..e0ac9eec1f2 100644
--- a/lib/gitlab/omniauth_initializer.rb
+++ b/lib/gitlab/omniauth_initializer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class OmniauthInitializer
def initialize(devise_config)
diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb
index d09bce642b0..ce4ba9f752b 100644
--- a/lib/gitlab/optimistic_locking.rb
+++ b/lib/gitlab/optimistic_locking.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module OptimisticLocking
module_function
diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb
index fc3f21233dd..bc467486eee 100644
--- a/lib/gitlab/other_markup.rb
+++ b/lib/gitlab/other_markup.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Parser/renderer for markups without other special support code.
module OtherMarkup
diff --git a/lib/gitlab/otp_key_rotator.rb b/lib/gitlab/otp_key_rotator.rb
index ca5d49eedb9..1d3200aa099 100644
--- a/lib/gitlab/otp_key_rotator.rb
+++ b/lib/gitlab/otp_key_rotator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# The +otp_key_base+ param is used to encrypt the User#otp_secret attribute.
#
diff --git a/lib/gitlab/pages.rb b/lib/gitlab/pages.rb
index 981ef8faa9a..16df0700b08 100644
--- a/lib/gitlab/pages.rb
+++ b/lib/gitlab/pages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Pages
VERSION = File.read(Rails.root.join("GITLAB_PAGES_VERSION")).strip.freeze
diff --git a/lib/gitlab/pages_client.rb b/lib/gitlab/pages_client.rb
index 7b358a3bd1b..3626e53f84c 100644
--- a/lib/gitlab/pages_client.rb
+++ b/lib/gitlab/pages_client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class PagesClient
class << self
diff --git a/lib/gitlab/pages_transfer.rb b/lib/gitlab/pages_transfer.rb
index fb215f27cbd..a70dc826f97 100644
--- a/lib/gitlab/pages_transfer.rb
+++ b/lib/gitlab/pages_transfer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class PagesTransfer < ProjectTransfer
def root_dir
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index a78314afdb2..44025650de0 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module PathRegex
extend self
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index fda61000f6a..4b0c7b5c7f8 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module PerformanceBar
ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze
diff --git a/lib/gitlab/plugin.rb b/lib/gitlab/plugin.rb
index 0d1cb16b378..23353f36025 100644
--- a/lib/gitlab/plugin.rb
+++ b/lib/gitlab/plugin.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Plugin
def self.files
diff --git a/lib/gitlab/plugin_logger.rb b/lib/gitlab/plugin_logger.rb
index c4f6ec3e21d..df3bd56fd2f 100644
--- a/lib/gitlab/plugin_logger.rb
+++ b/lib/gitlab/plugin_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class PluginLogger < Gitlab::Logger
def self.file_name_noext
diff --git a/lib/gitlab/polling_interval.rb b/lib/gitlab/polling_interval.rb
index fe4bdfe3831..0f69990df63 100644
--- a/lib/gitlab/polling_interval.rb
+++ b/lib/gitlab/polling_interval.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class PollingInterval
HEADER_NAME = 'Poll-Interval'.freeze
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index d0cb7c1a7cf..7fa00d0c68c 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fileutils'
require 'open3'
@@ -11,7 +13,7 @@ module Gitlab
def popen(cmd, path = nil, vars = {}, &block)
result = popen_with_detail(cmd, path, vars, &block)
- [result.stdout << result.stderr, result.status&.exitstatus]
+ ["#{result.stdout}#{result.stderr}", result.status&.exitstatus]
end
# Returns Result
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 15391b1330b..4a62f367835 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
module Gitlab
module Profiler
FILTERED_STRING = '[FILTERED]'.freeze
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 71e9cc7f238..3a202d915e3 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ProjectSearchResults < SearchResults
attr_reader :project, :repository_ref
@@ -57,7 +59,8 @@ module Gitlab
ref = nil
filename = nil
basename = nil
- data = ""
+
+ data = []
startline = 0
result.each_line.each_with_index do |line, index|
@@ -78,7 +81,7 @@ module Gitlab
basename: basename,
ref: ref,
startline: startline,
- data: data,
+ data: data.join,
project_id: project ? project.id : nil
)
end
diff --git a/lib/gitlab/project_service_logger.rb b/lib/gitlab/project_service_logger.rb
index e84dca97962..9b0357d3161 100644
--- a/lib/gitlab/project_service_logger.rb
+++ b/lib/gitlab/project_service_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ProjectServiceLogger < Gitlab::JsonLogger
def self.file_name_noext
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index 08f6a54776f..3bfd6ee892c 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ProjectTemplate
attr_reader :title, :name, :description, :preview
diff --git a/lib/gitlab/project_transfer.rb b/lib/gitlab/project_transfer.rb
index 690c38737c0..d8f1d1e2316 100644
--- a/lib/gitlab/project_transfer.rb
+++ b/lib/gitlab/project_transfer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# This class is used to move local, unhashed files owned by projects to their new location
class ProjectTransfer
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index b66253a10e0..45828c77a33 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Helper methods to interact with Prometheus network services & resources
class PrometheusClient
diff --git a/lib/gitlab/protocol_access.rb b/lib/gitlab/protocol_access.rb
index 2819c7d062c..efeb1e07d49 100644
--- a/lib/gitlab/protocol_access.rb
+++ b/lib/gitlab/protocol_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module ProtocolAccess
def self.allowed?(protocol)
diff --git a/lib/gitlab/proxy_http_connection_adapter.rb b/lib/gitlab/proxy_http_connection_adapter.rb
index d682289b632..82213098672 100644
--- a/lib/gitlab/proxy_http_connection_adapter.rb
+++ b/lib/gitlab/proxy_http_connection_adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class is part of the Gitlab::HTTP wrapper. Depending on the value
# of the global setting allow_local_requests_from_hooks_and_services this adapter
# will allow/block connection to internal IPs and/or urls.
diff --git a/lib/gitlab/query_limiting.rb b/lib/gitlab/query_limiting.rb
index 9f69a9e4a39..31e6b120e45 100644
--- a/lib/gitlab/query_limiting.rb
+++ b/lib/gitlab/query_limiting.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module QueryLimiting
# Returns true if we should enable tracking of query counts.
diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb
index c9efa28d7e7..6559c3e3c57 100644
--- a/lib/gitlab/recaptcha.rb
+++ b/lib/gitlab/recaptcha.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Recaptcha
def self.load_configurations!
diff --git a/lib/gitlab/reference_counter.rb b/lib/gitlab/reference_counter.rb
index bb26f1b610a..d2dbc6f5ef5 100644
--- a/lib/gitlab/reference_counter.rb
+++ b/lib/gitlab/reference_counter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ReferenceCounter
REFERENCE_EXPIRE_TIME = 600
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 9ff82d628c0..00f817c2399 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 0f26fcfe8cb..7a1a2eaf6c0 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Regex
extend self
diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb
index 4888184403c..202d310e237 100644
--- a/lib/gitlab/repo_path.rb
+++ b/lib/gitlab/repo_path.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module RepoPath
NotFoundError = Class.new(StandardError)
diff --git a/lib/gitlab/repository_cache.rb b/lib/gitlab/repository_cache.rb
index a03ce07b6a1..56007574b1b 100644
--- a/lib/gitlab/repository_cache.rb
+++ b/lib/gitlab/repository_cache.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Interface to the Redis-backed cache store
module Gitlab
class RepositoryCache
@@ -6,7 +8,7 @@ module Gitlab
def initialize(repository, extra_namespace: nil, backend: Rails.cache)
@repository = repository
@namespace = "#{repository.full_path}:#{repository.project.id}"
- @namespace += ":#{extra_namespace}" if extra_namespace
+ @namespace = "#{@namespace}:#{extra_namespace}" if extra_namespace
@backend = backend
end
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
index d95024fccf7..931298b5117 100644
--- a/lib/gitlab/repository_cache_adapter.rb
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module RepositoryCacheAdapter
extend ActiveSupport::Concern
diff --git a/lib/gitlab/repository_check_logger.rb b/lib/gitlab/repository_check_logger.rb
index 485b596ca57..e90b0a002af 100644
--- a/lib/gitlab/repository_check_logger.rb
+++ b/lib/gitlab/repository_check_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class RepositoryCheckLogger < Gitlab::Logger
def self.file_name_noext
diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb
index 8562d4515aa..f8f8ec789ce 100644
--- a/lib/gitlab/request_context.rb
+++ b/lib/gitlab/request_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class RequestContext
class << self
diff --git a/lib/gitlab/request_forgery_protection.rb b/lib/gitlab/request_forgery_protection.rb
index a502ad8a541..b1e478093d3 100644
--- a/lib/gitlab/request_forgery_protection.rb
+++ b/lib/gitlab/request_forgery_protection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# A module to check CSRF tokens in requests.
# It's used in API helpers and OmniAuth.
# Usage: GitLab::RequestForgeryProtection.call(env)
diff --git a/lib/gitlab/request_profiler.rb b/lib/gitlab/request_profiler.rb
index 0c9ab759e81..64593153686 100644
--- a/lib/gitlab/request_profiler.rb
+++ b/lib/gitlab/request_profiler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fileutils'
module Gitlab
diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb
index f3952657983..a555bf1d812 100644
--- a/lib/gitlab/route_map.rb
+++ b/lib/gitlab/route_map.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class RouteMap
FormatError = Class.new(StandardError)
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
index 2c994536060..3b05f181ed2 100644
--- a/lib/gitlab/routing.rb
+++ b/lib/gitlab/routing.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Routing
extend ActiveSupport::Concern
@@ -47,7 +49,7 @@ module Gitlab
#
# `request.fullpath` includes the querystring
new_path = request.path.sub(%r{/#{path}(/*)(?!.*#{path})}, "/-/#{path}\\1")
- new_path << "?#{request.query_string}" if request.query_string.present?
+ new_path = "#{new_path}?#{request.query_string}" if request.query_string.present?
new_path
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index d1cf8e10228..5ce3eda2ccb 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class SearchResults
class FoundBlob
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 98f005cb61b..84a51773276 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# :nocov:
module DeliverNever
def deliver_later
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 6381e94c1d2..24e3866128b 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Sentry
def self.enabled?
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index 5b68e4470cd..2b7e12639be 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'toml-rb'
module Gitlab
@@ -30,7 +32,10 @@ module Gitlab
end
if Rails.env.test?
- storages << { name: 'test_second_storage', path: Rails.root.join('tmp', 'tests', 'second_storage').to_s }
+ storage_path = Rails.root.join('tmp', 'tests', 'second_storage').to_s
+
+ FileUtils.mkdir(storage_path) unless File.exist?(storage_path)
+ storages << { name: 'test_second_storage', path: storage_path }
end
config = { socket_path: address.sub(/\Aunix:/, ''), storage: storages }
diff --git a/lib/gitlab/shard_health_cache.rb b/lib/gitlab/shard_health_cache.rb
index 3f03f46d4b1..6612347438e 100644
--- a/lib/gitlab/shard_health_cache.rb
+++ b/lib/gitlab/shard_health_cache.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class ShardHealthCache
HEALTHY_SHARDS_KEY = 'gitlab-healthy-shards'.freeze
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 89d2028d7b0..16c1edb2f11 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Gitaly note: SSH key operations are not part of Gitaly so will never be migrated.
require 'securerandom'
diff --git a/lib/gitlab/shell_adapter.rb b/lib/gitlab/shell_adapter.rb
index 053dd4ab9e0..59fc6ee8dc8 100644
--- a/lib/gitlab/shell_adapter.rb
+++ b/lib/gitlab/shell_adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == GitLab Shell mixin
#
# Provide a shortcut to Gitlab::Shell instance by gitlab_shell
diff --git a/lib/gitlab/sherlock.rb b/lib/gitlab/sherlock.rb
index 6360527a7aa..a1471c9de47 100644
--- a/lib/gitlab/sherlock.rb
+++ b/lib/gitlab/sherlock.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'securerandom'
module Gitlab
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
index c3d7814551c..01f60a98ad8 100644
--- a/lib/gitlab/sidekiq_config.rb
+++ b/lib/gitlab/sidekiq_config.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'yaml'
require 'set'
diff --git a/lib/gitlab/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb
index c1dab87a432..ce82a6f04bb 100644
--- a/lib/gitlab/sidekiq_logger.rb
+++ b/lib/gitlab/sidekiq_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class SidekiqLogger < Gitlab::Logger
def self.file_name_noext
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index a1f689d94d9..583a970bf4e 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# The SidekiqStatus module and its child classes can be used for checking if a
# Sidekiq job has been processed or not.
diff --git a/lib/gitlab/sidekiq_versioning.rb b/lib/gitlab/sidekiq_versioning.rb
index 9683214ec18..8164a5a9d7a 100644
--- a/lib/gitlab/sidekiq_versioning.rb
+++ b/lib/gitlab/sidekiq_versioning.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module SidekiqVersioning
def self.install!
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index 95e37dfbdab..e360b552f89 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class SnippetSearchResults < SearchResults
include SnippetsHelper
diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb
index 6f63ea91ae8..47571239b5c 100644
--- a/lib/gitlab/ssh_public_key.rb
+++ b/lib/gitlab/ssh_public_key.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class SSHPublicKey
Technology = Struct.new(:name, :key_class, :supported_sizes)
@@ -26,7 +28,7 @@ module Gitlab
return key_content if parts.empty?
- parts.each_with_object("#{ssh_type} ").with_index do |(part, content), index|
+ parts.each_with_object(+"#{ssh_type} ").with_index do |(part, content), index|
content << part
if Gitlab::SSHPublicKey.new(content).valid?
diff --git a/lib/gitlab/string_placeholder_replacer.rb b/lib/gitlab/string_placeholder_replacer.rb
index 9a2219b7d77..62621255a53 100644
--- a/lib/gitlab/string_placeholder_replacer.rb
+++ b/lib/gitlab/string_placeholder_replacer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class StringPlaceholderReplacer
# This method accepts the following paras
diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb
index c6ad997a4d4..780fe4c7725 100644
--- a/lib/gitlab/string_range_marker.rb
+++ b/lib/gitlab/string_range_marker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class StringRangeMarker
attr_accessor :raw_line, :rich_line, :html_escaped
diff --git a/lib/gitlab/string_regex_marker.rb b/lib/gitlab/string_regex_marker.rb
index 1c87c56c45e..f1982ff914c 100644
--- a/lib/gitlab/string_regex_marker.rb
+++ b/lib/gitlab/string_regex_marker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class StringRegexMarker < StringRangeMarker
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb
index 922418966e9..224bb648d8f 100644
--- a/lib/gitlab/task_helpers.rb
+++ b/lib/gitlab/task_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rainbow/ext/string'
require 'gitlab/utils/strong_memoize'
@@ -39,7 +41,7 @@ module Gitlab
File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1]
end
- os_name.try(:squish!)
+ os_name.try(:squish)
end
# Prompt the user to input something
diff --git a/lib/gitlab/tcp_checker.rb b/lib/gitlab/tcp_checker.rb
index 6e24e46d0ea..f37a044b607 100644
--- a/lib/gitlab/tcp_checker.rb
+++ b/lib/gitlab/tcp_checker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class TcpChecker
attr_reader :remote_host, :remote_port, :local_host, :local_port, :error
diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb
index 4456217017f..699d747892c 100644
--- a/lib/gitlab/template/base_template.rb
+++ b/lib/gitlab/template/base_template.rb
@@ -1,7 +1,7 @@
module Gitlab
module Template
class BaseTemplate
- attr_reader :category
+ attr_accessor :category
def initialize(path, project = nil, category: nil)
@path = path
diff --git a/lib/gitlab/template_helper.rb b/lib/gitlab/template_helper.rb
index fc498dde723..b0e01697a66 100644
--- a/lib/gitlab/template_helper.rb
+++ b/lib/gitlab/template_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module TemplateHelper
def prepare_template_environment(file)
diff --git a/lib/gitlab/temporarily_allow.rb b/lib/gitlab/temporarily_allow.rb
index 8d7dacc6c54..000f8ca699d 100644
--- a/lib/gitlab/temporarily_allow.rb
+++ b/lib/gitlab/temporarily_allow.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module TemporarilyAllow
TEMPORARILY_ALLOW_MUTEX = Mutex.new
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
index 694b01b272c..63860b9cb26 100644
--- a/lib/gitlab/themes.rb
+++ b/lib/gitlab/themes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# Module containing GitLab's application theme definitions and helper methods
# for accessing them.
diff --git a/lib/gitlab/time_tracking_formatter.rb b/lib/gitlab/time_tracking_formatter.rb
index d615c24149a..cc206010e74 100644
--- a/lib/gitlab/time_tracking_formatter.rb
+++ b/lib/gitlab/time_tracking_formatter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module TimeTrackingFormatter
extend self
diff --git a/lib/gitlab/timeless.rb b/lib/gitlab/timeless.rb
index 76a1808c8ac..4f974c98c71 100644
--- a/lib/gitlab/timeless.rb
+++ b/lib/gitlab/timeless.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Timeless
def self.timeless(model, &block)
diff --git a/lib/gitlab/tree_summary.rb b/lib/gitlab/tree_summary.rb
index c2955cd374c..453d78e2f7b 100644
--- a/lib/gitlab/tree_summary.rb
+++ b/lib/gitlab/tree_summary.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class TreeSummary
include ::Gitlab::Utils::StrongMemoize
diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb
index dc2d91dfa23..ba1137313d8 100644
--- a/lib/gitlab/untrusted_regexp.rb
+++ b/lib/gitlab/untrusted_regexp.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
# An untrusted regular expression is any regexp containing patterns sourced
# from user input.
diff --git a/lib/gitlab/update_path_error.rb b/lib/gitlab/update_path_error.rb
index 8947ecfb92e..bc066bf4143 100644
--- a/lib/gitlab/update_path_error.rb
+++ b/lib/gitlab/update_path_error.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
UpdatePathError = Class.new(StandardError)
end
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
index 024be6aca44..ccab0e4dd73 100644
--- a/lib/gitlab/upgrader.rb
+++ b/lib/gitlab/upgrader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class Upgrader
def execute
diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb
index 7d7400bdabf..e0e7084e27e 100644
--- a/lib/gitlab/uploads_transfer.rb
+++ b/lib/gitlab/uploads_transfer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class UploadsTransfer < ProjectTransfer
def root_dir
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 3b483f27e70..7735b736689 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'resolv'
module Gitlab
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index e64033b0dba..f86d599e4cb 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class UrlBuilder
include Gitlab::Routing
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index 29672d68cad..035268bc4f2 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class UrlSanitizer
ALLOWED_SCHEMES = %w[http https ssh git].freeze
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 5097c3253c9..cc0817bdcd2 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class UsageData
class << self
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 27560abfb96..980a8014409 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class UserAccess
extend Gitlab::Cache::RequestCache
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index aeda66763e8..2c92458f777 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Utils
extend self
diff --git a/lib/gitlab/version_info.rb b/lib/gitlab/version_info.rb
index 6ee41e85cc9..aa6d5310161 100644
--- a/lib/gitlab/version_info.rb
+++ b/lib/gitlab/version_info.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class VersionInfo
include Comparable
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 2612208a927..a3c7de87765 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Gitlab::VisibilityLevel module
#
# Define allowed public modes that can be used for
diff --git a/lib/gitlab/wiki_file_finder.rb b/lib/gitlab/wiki_file_finder.rb
index f97278f05cd..a00cd65594c 100644
--- a/lib/gitlab/wiki_file_finder.rb
+++ b/lib/gitlab/wiki_file_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
class WikiFileFinder < FileFinder
attr_reader :repository
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 30a8c3578d8..e1f777e9cd1 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'base64'
require 'json'
require 'securerandom'
@@ -63,7 +65,7 @@ module Gitlab
def send_git_archive(repository, ref:, format:, append_sha:)
format ||= 'tar.gz'
- format.downcase!
+ format = format.downcase
params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha)
raise "Repository or ref not found" if params.empty?
diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb
index 5ecd6169ecf..2e98227a05e 100644
--- a/lib/haml_lint/inline_javascript.rb
+++ b/lib/haml_lint/inline_javascript.rb
@@ -2,9 +2,9 @@
# frozen_string_literal: true
unless Rails.env.production?
- require 'haml_lint/haml_visitor'
- require 'haml_lint/linter'
- require 'haml_lint/linter_registry'
+ require_dependency 'haml_lint/haml_visitor'
+ require_dependency 'haml_lint/linter'
+ require_dependency 'haml_lint/linter_registry'
module HamlLint
class Linter::InlineJavaScript < Linter
diff --git a/lib/quality/helm_client.rb b/lib/quality/helm_client.rb
index 49d953da681..cf1f03b35b5 100644
--- a/lib/quality/helm_client.rb
+++ b/lib/quality/helm_client.rb
@@ -5,9 +5,13 @@ require_relative '../gitlab/popen' unless defined?(Gitlab::Popen)
module Quality
class HelmClient
+ CommandFailedError = Class.new(StandardError)
+
attr_reader :namespace
- Release = Struct.new(:name, :revision, :last_update, :status, :chart, :namespace) do
+ RELEASE_JSON_ATTRIBUTES = %w[Name Revision Updated Status Chart AppVersion Namespace].freeze
+
+ Release = Struct.new(:name, :revision, :last_update, :status, :chart, :app_version, :namespace) do
def revision
@revision ||= self[:revision].to_i
end
@@ -17,22 +21,24 @@ module Quality
end
end
- def initialize(namespace: ENV['KUBE_NAMESPACE'])
+ # A single page of data and the corresponding page number.
+ Page = Struct.new(:releases, :number)
+
+ def initialize(namespace:)
@namespace = namespace
end
def releases(args: [])
- command = ['list', %(--namespace "#{namespace}"), *args]
-
- run_command(command)
- .stdout
- .lines
- .select { |line| line.include?(namespace) }
- .map { |line| Release.new(*line.split(/\t/).map(&:strip)) }
+ each_release(args)
end
def delete(release_name:)
- run_command(['delete', '--purge', release_name])
+ run_command([
+ 'delete',
+ %(--tiller-namespace "#{namespace}"),
+ '--purge',
+ release_name
+ ])
end
private
@@ -41,7 +47,67 @@ module Quality
final_command = ['helm', *command].join(' ')
puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output
- Gitlab::Popen.popen_with_detail([final_command])
+ result = Gitlab::Popen.popen_with_detail([final_command])
+
+ if result.status.success?
+ result.stdout.chomp.freeze
+ else
+ raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}"
+ end
+ end
+
+ def raw_releases(args = [])
+ command = [
+ 'list',
+ %(--namespace "#{namespace}"),
+ %(--tiller-namespace "#{namespace}" --output json),
+ *args
+ ]
+ json = JSON.parse(run_command(command))
+
+ releases = json['Releases'].map do |json_release|
+ Release.new(*json_release.values_at(*RELEASE_JSON_ATTRIBUTES))
+ end
+
+ [releases, json['Next']]
+ rescue JSON::ParserError => ex
+ puts "Ignoring this JSON parsing error: #{ex}" # rubocop:disable Rails/Output
+ [[], nil]
+ end
+
+ # Fetches data from Helm and yields a Page object for every page
+ # of data, without loading all of them into memory.
+ #
+ # method - The Octokit method to use for getting the data.
+ # args - Arguments to pass to the `helm list` command.
+ def each_releases_page(args, &block)
+ return to_enum(__method__, args) unless block_given?
+
+ page = 1
+ offset = ''
+
+ loop do
+ final_args = args.dup
+ final_args << "--offset #{offset}" unless offset.to_s.empty?
+ collection, offset = raw_releases(final_args)
+
+ yield Page.new(collection, page += 1)
+
+ break if offset.to_s.empty?
+ end
+ end
+
+ # Iterates over all of the releases.
+ #
+ # args - Any arguments to pass to the `helm list` command.
+ def each_release(args, &block)
+ return to_enum(__method__, args) unless block_given?
+
+ each_releases_page(args) do |page|
+ page.releases.each do |release|
+ yield release
+ end
+ end
end
end
end
diff --git a/lib/quality/kubernetes_client.rb b/lib/quality/kubernetes_client.rb
index e366a688e3e..2ff9e811425 100644
--- a/lib/quality/kubernetes_client.rb
+++ b/lib/quality/kubernetes_client.rb
@@ -4,19 +4,22 @@ require_relative '../gitlab/popen' unless defined?(Gitlab::Popen)
module Quality
class KubernetesClient
+ CommandFailedError = Class.new(StandardError)
+
attr_reader :namespace
- def initialize(namespace: ENV['KUBE_NAMESPACE'])
+ def initialize(namespace:)
@namespace = namespace
end
def cleanup(release_name:)
- command = ['kubectl']
- command << %(-n "#{namespace}" get ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa 2>&1)
- command << '|' << %(grep "#{release_name}")
- command << '|' << "awk '{print $1}'"
- command << '|' << %(xargs kubectl -n "#{namespace}" delete)
- command << '||' << 'true'
+ command = [
+ %(--namespace "#{namespace}"),
+ 'delete',
+ 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa',
+ '--now',
+ %(-l release="#{release_name}")
+ ]
run_command(command)
end
@@ -24,9 +27,16 @@ module Quality
private
def run_command(command)
- puts "Running command: `#{command.join(' ')}`" # rubocop:disable Rails/Output
+ final_command = ['kubectl', *command].join(' ')
+ puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output
+
+ result = Gitlab::Popen.popen_with_detail([final_command])
- Gitlab::Popen.popen_with_detail(command)
+ if result.status.success?
+ result.stdout.chomp.freeze
+ else
+ raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}"
+ end
end
end
end
diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake
index fc59b3f937d..a16d4c47273 100644
--- a/lib/tasks/import.rake
+++ b/lib/tasks/import.rake
@@ -9,7 +9,7 @@ class GithubImport
def initialize(token, gitlab_username, project_path, extras)
@options = { token: token }
@project_path = project_path
- @current_user = User.find_by(username: gitlab_username)
+ @current_user = UserFinder.new(gitlab_username).find_by_username
raise "GitLab user #{gitlab_username} not found. Please specify a valid username." unless @current_user
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 363654b2ac0..26270595c6a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -330,6 +330,9 @@ msgstr ""
msgid "Add Readme"
msgstr ""
+msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
+msgstr ""
+
msgid "Add a table"
msgstr ""
@@ -1214,6 +1217,9 @@ msgstr ""
msgid "CiStatusLabel|created"
msgstr ""
+msgid "CiStatusLabel|delayed"
+msgstr ""
+
msgid "CiStatusLabel|failed"
msgstr ""
@@ -1229,9 +1235,6 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
-msgid "CiStatusLabel|scheduled"
-msgstr ""
-
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1250,6 +1253,9 @@ msgstr ""
msgid "CiStatusText|created"
msgstr ""
+msgid "CiStatusText|delayed"
+msgstr ""
+
msgid "CiStatusText|failed"
msgstr ""
@@ -1262,9 +1268,6 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
-msgid "CiStatusText|scheduled"
-msgstr ""
-
msgid "CiStatusText|skipped"
msgstr ""
@@ -2049,6 +2052,9 @@ msgstr ""
msgid "Create project label"
msgstr ""
+msgid "Create your first page"
+msgstr ""
+
msgid "CreateTag|Tag"
msgstr ""
@@ -2172,7 +2178,7 @@ msgstr ""
msgid "DelayedJobs|Unschedule"
msgstr ""
-msgid "DelayedJobs|scheduled"
+msgid "DelayedJobs|delayed"
msgstr ""
msgid "Delete"
@@ -3593,6 +3599,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List view"
+msgstr ""
+
msgid "List your Bitbucket Server repositories"
msgstr ""
@@ -4021,6 +4030,12 @@ msgstr ""
msgid "No"
msgstr ""
+msgid "No Assignee"
+msgstr ""
+
+msgid "No Label"
+msgstr ""
+
msgid "No assignee"
msgstr ""
@@ -4126,6 +4141,12 @@ msgstr ""
msgid "Notes|Are you sure you want to cancel creating this comment?"
msgstr ""
+msgid "Notes|Show all activity"
+msgstr ""
+
+msgid "Notes|Show comments only"
+msgstr ""
+
msgid "Notification events"
msgstr ""
@@ -5583,6 +5604,9 @@ msgstr ""
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr ""
+msgid "Something went wrong while fetching comments. Please try again."
+msgstr ""
+
msgid "Something went wrong while fetching the projects."
msgstr ""
@@ -6107,7 +6131,7 @@ msgstr ""
msgid "This is a confidential issue."
msgstr ""
-msgid "This is a scheduled to run in "
+msgid "This is a delayed to run in "
msgstr ""
msgid "This is the author's first Merge Request to this project."
@@ -6197,6 +6221,9 @@ msgstr ""
msgid "This project does not belong to a group and can therefore not make use of group Runners."
msgstr ""
+msgid "This project does not have a wiki homepage yet"
+msgstr ""
+
msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
msgstr ""
@@ -6495,6 +6522,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Tree view"
+msgstr ""
+
msgid "Trending"
msgstr ""
@@ -6576,6 +6606,9 @@ msgstr ""
msgid "Up to date"
msgstr ""
+msgid "Upcoming"
+msgstr ""
+
msgid "Update"
msgstr ""
@@ -6708,6 +6741,9 @@ msgstr ""
msgid "Version"
msgstr ""
+msgid "View app"
+msgstr ""
+
msgid "View file @ "
msgstr ""
diff --git a/package.json b/package.json
index 8ec47bc2837..086617dc265 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
"postinstall": "node ./scripts/frontend/postinstall.js",
- "prettier-staged": "node ./scripts/frontend/prettier.js",
+ "prettier-staged": "node ./scripts/frontend/prettier.js check",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
@@ -24,12 +24,11 @@
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.1.0",
- "@gitlab-org/gitlab-svgs": "^1.32.0",
+ "@gitlab-org/gitlab-svgs": "^1.33.0",
"@gitlab-org/gitlab-ui": "^1.8.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.4",
- "blackst0ne-mermaid": "^7.1.0-fixed",
"bootstrap": "4.1.1",
"brace-expansion": "^1.1.8",
"cache-loader": "^1.2.2",
@@ -71,6 +70,7 @@
"jszip-utils": "^0.0.2",
"katex": "^0.9.0",
"marked": "^0.3.12",
+ "mermaid": "^8.0.0-rc.8",
"monaco-editor": "^0.14.3",
"monaco-editor-webpack-plugin": "^1.5.4",
"mousetrap": "^1.4.6",
@@ -126,7 +126,6 @@
"eslint-plugin-jasmine": "^2.10.1",
"gettext-extractor": "^3.3.2",
"gettext-extractor-vue": "^4.0.1",
- "ignore": "^3.3.7",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
diff --git a/qa/qa.rb b/qa/qa.rb
index 7feca22478a..36a37dbb270 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -18,6 +18,7 @@ module QA
autoload :Address, 'qa/runtime/address'
autoload :Path, 'qa/runtime/path'
autoload :Fixtures, 'qa/runtime/fixtures'
+ autoload :Logger, 'qa/runtime/logger'
module API
autoload :Client, 'qa/runtime/api/client'
@@ -324,6 +325,14 @@ module QA
end
end
end
+
+ # Classes that provide support to other parts of the framework.
+ #
+ module Support
+ module Page
+ autoload :Logging, 'qa/support/page/logging'
+ end
+ end
end
QA::Runtime::Release.extend_autoloads!
diff --git a/qa/qa/factory/README.md b/qa/qa/factory/README.md
index c56c7c43129..10140e39510 100644
--- a/qa/qa/factory/README.md
+++ b/qa/qa/factory/README.md
@@ -254,8 +254,7 @@ module QA
project.name = 'project-to-create-a-shirt'
end
- # Attribute inherited from the Shirt factory if present,
- # or from the Browser UI otherwise (using the block)
+ # Attribute from the Browser UI (using the block)
product :brand do
Page::Shirt::Show.perform do |shirt_show|
shirt_show.fetch_brand_from_page
@@ -347,8 +346,7 @@ module QA
project.name = 'project-to-create-a-shirt'
end
- # Attribute fetched from the API response if present if present,
- # or from the Shirt factory if present,
+ # Attribute fetched from the API response if present,
# or from the Browser UI otherwise (using the block)
product :brand do
Page::Shirt::Show.perform do |shirt_show|
@@ -356,7 +354,7 @@ module QA
end
end
- # Attribute fetched from the API response if present if present,
+ # Attribute fetched from the API response if present,
# or from the Shirt factory if present,
# or a QA::Factory::Product::NoValueError is raised otherwise
product :name
@@ -414,9 +412,9 @@ end
**Notes on attributes precedence:**
- attributes from the API response take precedence over attributes from the
+ Browser UI
+- attributes from the Browser UI take precedence over attributes from the
factory (i.e inherited)
-- attributes from the factory (i.e inherited) take precedence over attributes
- from the Browser UI
- attributes without a value will raise a `QA::Factory::Product::NoValueError` error
## Creating resources in your tests
diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb
index a8ecac2a1e6..e1dc23d350d 100644
--- a/qa/qa/factory/base.rb
+++ b/qa/qa/factory/base.rb
@@ -63,7 +63,7 @@ module QA
private_class_method :do_fabricate!
def self.log_fabrication(method, factory, parents, args)
- return yield unless Runtime::Env.verbose?
+ return yield unless Runtime::Env.debug?
start = Time.now
prefix = "==#{'=' * parents.size}>"
diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb
index 6c5088f1da5..703c78daa99 100644
--- a/qa/qa/factory/repository/push.rb
+++ b/qa/qa/factory/repository/push.rb
@@ -30,6 +30,14 @@ module QA
@directory = dir
end
+ def files=(files)
+ if !files.is_a?(Array) || files.empty?
+ raise ArgumentError, "Please provide an array of hashes e.g.: [{name: 'file1', content: 'foo'}]"
+ end
+
+ @files = files
+ end
+
def fabricate!
Git::Repository.perform do |repository|
if ssh_key
@@ -63,6 +71,10 @@ module QA
@directory.each_child do |f|
repository.add_file(f.basename, f.read) if f.file?
end
+ elsif @files
+ @files.each do |f|
+ repository.add_file(f[:name], f[:content])
+ end
else
repository.add_file(file_name, file_content)
end
diff --git a/qa/qa/factory/resource/merge_request.rb b/qa/qa/factory/resource/merge_request.rb
index 18046c7a8b2..d30da8a3db0 100644
--- a/qa/qa/factory/resource/merge_request.rb
+++ b/qa/qa/factory/resource/merge_request.rb
@@ -30,6 +30,7 @@ module QA
push.project = factory.project
push.branch_name = factory.target_branch
push.remote_branch = factory.source_branch
+ push.new_branch = false
push.file_name = "added_file.txt"
push.file_content = "File Added"
end
diff --git a/qa/qa/factory/settings/hashed_storage.rb b/qa/qa/factory/settings/hashed_storage.rb
index f2e58a3ea38..5e8f883e25f 100644
--- a/qa/qa/factory/settings/hashed_storage.rb
+++ b/qa/qa/factory/settings/hashed_storage.rb
@@ -9,7 +9,7 @@ module QA
Page::Main::Menu.act { go_to_admin_area }
Page::Admin::Menu.act { go_to_repository_settings }
- Page::Admin::Settings::Main.perform do |setting|
+ Page::Admin::Settings::Repository.perform do |setting|
setting.expand_repository_storage do |page|
page.enable_hashed_storage
page.save_settings
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 14cb8125fdb..c6a8891d398 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -1,14 +1,24 @@
+# frozen_string_literal: true
+
require 'cgi'
require 'uri'
require 'open3'
+require 'fileutils'
+require 'tmpdir'
module QA
module Git
class Repository
include Scenario::Actable
+ attr_writer :password
+ attr_accessor :env_vars
+
def initialize
- @ssh_cmd = ""
+ # We set HOME to the current working directory (which is a
+ # temporary directory created in .perform()) so the temporarily dropped
+ # .netrc can be utilised
+ self.env_vars = [%Q{HOME="#{File.dirname(netrc_file_path)}"}]
end
def self.perform(*args)
@@ -21,36 +31,27 @@ module QA
@uri = URI(address)
end
- def username=(name)
- @username = name
- @uri.user = name
- end
-
- def password=(pass)
- @password = pass
- @uri.password = CGI.escape(pass).gsub('+', '%20')
+ def username=(username)
+ @username = username
+ @uri.user = username
end
def use_default_credentials
- if ::QA::Runtime::User.ldap_user?
- self.username = Runtime::User.ldap_username
- self.password = Runtime::User.ldap_password
- else
- self.username = Runtime::User.username
- self.password = Runtime::User.password
- end
+ self.username, self.password = default_credentials
+
+ add_credentials_to_netrc unless ssh_key_set?
end
def clone(opts = '')
- run_and_redact_credentials(build_git_command("git clone #{opts} #{@uri} ./"))
+ run("git clone #{opts} #{uri} ./")
end
def checkout(branch_name)
- `git checkout "#{branch_name}"`
+ run(%Q{git checkout "#{branch_name}"})
end
def checkout_new_branch(branch_name)
- `git checkout -b "#{branch_name}"`
+ run(%Q{git checkout -b "#{branch_name}"})
end
def shallow_clone
@@ -58,12 +59,10 @@ module QA
end
def configure_identity(name, email)
- `git config user.name #{name}`
- `git config user.email #{email}`
- end
+ run(%Q{git config user.name #{name}})
+ run(%Q{git config user.email #{email}})
- def configure_ssh_command(command)
- @ssh_cmd = "GIT_SSH_COMMAND='#{command}'"
+ add_credentials_to_netrc
end
def commit_file(name, contents, message)
@@ -74,54 +73,103 @@ module QA
def add_file(name, contents)
::File.write(name, contents)
- `git add #{name}`
+ run(%Q{git add #{name}})
end
def commit(message)
- `git commit -m "#{message}"`
+ run(%Q{git commit -m "#{message}"})
end
def push_changes(branch = 'master')
- output, _ = run_and_redact_credentials(build_git_command("git push #{@uri} #{branch}"))
-
- output
+ run("git push #{uri} #{branch}")
end
def commits
- `git log --oneline`.split("\n")
+ run('git log --oneline').split("\n")
end
def use_ssh_key(key)
@private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
- File.binwrite(@private_key_file, key.private_key)
- File.chmod(0700, @private_key_file)
+ File.binwrite(private_key_file, key.private_key)
+ File.chmod(0700, private_key_file)
@known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
keyscan_params = ['-H']
- keyscan_params << "-p #{@uri.port}" if @uri.port
- keyscan_params << @uri.host
- run_and_redact_credentials("ssh-keyscan #{keyscan_params.join(' ')} >> #{@known_hosts_file.path}")
+ keyscan_params << "-p #{uri.port}" if uri.port
+ keyscan_params << uri.host
+ run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
- configure_ssh_command("ssh -i #{@private_key_file.path} -o UserKnownHostsFile=#{@known_hosts_file.path}")
+ self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
end
def delete_ssh_key
- return unless @private_key_file
+ return unless ssh_key_set?
- @private_key_file.close(true)
- @known_hosts_file.close(true)
+ private_key_file.close(true)
+ known_hosts_file.close(true)
end
- def build_git_command(command_str)
- [@ssh_cmd, command_str].compact.join(' ')
+ private
+
+ attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file
+
+ def debug?
+ Runtime::Env.respond_to?(:verbose?) && Runtime::Env.verbose?
end
- private
+ def ssh_key_set?
+ !private_key_file.nil?
+ end
+
+ def run(command_str)
+ command = [env_vars, command_str, '2>&1'].compact.join(' ')
+ warn "DEBUG: command=[#{command}]" if debug?
+
+ output, _ = Open3.capture2(command)
+ output = output.chomp.gsub(/\s+$/, '')
+ warn "DEBUG: output=[#{output}]" if debug?
+
+ output
+ end
+
+ def default_credentials
+ if ::QA::Runtime::User.ldap_user?
+ [Runtime::User.ldap_username, Runtime::User.ldap_password]
+ else
+ [Runtime::User.username, Runtime::User.password]
+ end
+ end
+
+ def tmp_netrc_directory
+ @tmp_netrc_directory ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
+ end
+
+ def netrc_file_path
+ @netrc_file_path ||= File.join(tmp_netrc_directory, '.netrc')
+ end
+
+ def netrc_content
+ "machine #{uri.host} login #{username} password #{password}"
+ end
+
+ def netrc_already_contains_content?
+ File.exist?(netrc_file_path) &&
+ File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any?
+ end
+
+ def add_credentials_to_netrc
+ # Despite libcurl supporting a custom .netrc location through the
+ # CURLOPT_NETRC_FILE environment variable, git does not support it :(
+ # Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
+ #
+ # This will create a .netrc in the correct working directory, which is
+ # a temporary directory created in .perform()
+ #
+ return if netrc_already_contains_content?
- # Since the remote URL contains the credentials, and git occasionally
- # outputs the URL. Note that stderr is redirected to stdout.
- def run_and_redact_credentials(command)
- Open3.capture2("#{command} 2>&1 | sed -E 's#://[^@]+@#://****@#g'")
+ FileUtils.mkdir_p(tmp_netrc_directory)
+ File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
+ File.chmod(0600, netrc_file_path)
end
end
end
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 160ec58cf2c..91e229c4c8c 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -5,6 +5,7 @@ require 'capybara/dsl'
module QA
module Page
class Base
+ prepend Support::Page::Logging if Runtime::Env.debug?
include Capybara::DSL
include Scenario::Actable
extend SingleForwardable
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 5baf6439cfc..d688f15914c 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -4,30 +4,39 @@ module QA::Page
COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running
PASSED_STATUS = 'passed'.freeze
- view 'app/views/shared/builds/_build_output.html.haml' do
- element :build_output, '.js-build-output' # rubocop:disable QA/ElementWithPattern
- element :loading_animation, '.js-build-refresh' # rubocop:disable QA/ElementWithPattern
+ view 'app/assets/javascripts/jobs/components/job_app.vue' do
+ element :loading_animation
+ end
+
+ view 'app/assets/javascripts/jobs/components/job_log.vue' do
+ element :build_trace
end
view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do
- element :status_badge, 'ci-status' # rubocop:disable QA/ElementWithPattern
+ element :status_badge
end
def completed?
- COMPLETED_STATUSES.include? find('.ci-status').text
+ COMPLETED_STATUSES.include?(status_badge)
end
def passed?
- find('.ci-status').text == PASSED_STATUS
+ status_badge == PASSED_STATUS
end
def trace_loading?
- has_css?('.js-build-refresh')
+ has_element?(:loading_animation)
end
# Reminder: You may wish to wait for a particular job status before checking output
def output
- find('.js-build-output').text
+ find_element(:build_trace).text
+ end
+
+ private
+
+ def status_badge
+ find_element(:status_badge).text
end
end
end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index bc125d1af88..cb4a10e1b6a 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -87,6 +87,14 @@ module QA
end
end
+ def go_to_labels
+ hover_issues do
+ within_submenu do
+ click_element(:labels_link)
+ end
+ end
+ end
+
def click_merge_requests
within_sidebar do
click_link('Merge Requests')
@@ -105,8 +113,10 @@ module QA
end
end
- def go_to_labels
- hover_issues { click_element :labels_link }
+ def click_repository
+ within_sidebar do
+ click_link('Repository')
+ end
end
private
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index fcc4bb79c10..d6dddf03ffb 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -42,6 +42,10 @@ module QA
element :web_ide_button
end
+ view 'app/views/projects/tree/_tree_content.html.haml' do
+ element :file_tree
+ end
+
def project_name
find('.qa-project-name').text
end
@@ -51,6 +55,12 @@ module QA
click_element :new_file_option
end
+ def go_to_file(filename)
+ within_element(:file_tree) do
+ click_on filename
+ end
+ end
+
def switch_to_branch(branch_name)
find_element(:branches_select).click
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 533ed87453a..c7052a9f300 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
module Env
@@ -5,8 +7,12 @@ module QA
attr_writer :personal_access_token
- def verbose?
- enabled?(ENV['VERBOSE'], default: false)
+ def debug?
+ enabled?(ENV['QA_DEBUG'], default: false)
+ end
+
+ def log_destination
+ ENV['QA_LOG_PATH'] || $stdout
end
# set to 'false' to have Chrome run visibly instead of headless
diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb
new file mode 100644
index 00000000000..3baa24de0ec
--- /dev/null
+++ b/qa/qa/runtime/logger.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'logger'
+
+module QA
+ module Runtime
+ module Logger
+ extend SingleForwardable
+
+ def_delegators :logger, :debug, :info, :error, :warn, :fatal, :unknown
+
+ singleton_class.module_eval do
+ def logger
+ return @logger if @logger
+
+ @logger = ::Logger.new Runtime::Env.log_destination
+ @logger.level = ::Logger::DEBUG
+ @logger
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/1_manage/users_spec.rb b/qa/qa/specs/features/api/1_manage/users_spec.rb
index 3e3c9e859aa..ba1ba204d24 100644
--- a/qa/qa/specs/features/api/1_manage/users_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/users_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage do
+ context 'Manage' do
describe 'Users API' do
before(:context) do
@api_client = Runtime::API::Client.new(:gitlab)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
index ae196349c6b..dae2a9e0236 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
@@ -1,5 +1,5 @@
module QA
- context :manage, :smoke do
+ context 'Manage', :smoke do
describe 'basic user login' do
it 'user logs in using basic credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
index 217870531da..eb9e0297287 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :orchestrated, :ldap do
+ context 'Manage', :orchestrated, :ldap do
describe 'LDAP login' do
it 'user logs into GitLab using LDAP credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
index 6eda2c750d4..b1d641b507f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :orchestrated, :mattermost do
+ context 'Manage', :orchestrated, :mattermost do
describe 'Mattermost login' do
it 'user logs into Mattermost using GitLab OAuth' do
Runtime::Browser.visit(:gitlab, Page::Main::Login) do
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
index 8d5055aab45..87f0e9030d2 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :orchestrated, :instance_saml do
+ context 'Manage', :orchestrated, :instance_saml do
describe 'Instance wide SAML SSO' do
it 'User logs in to gitlab with SAML SSO' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
index fb6b4937554..45cb5df8252 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
@@ -16,13 +16,13 @@ module QA
end
end
- context :manage, :skip_signup_disabled do
+ context 'Manage', :skip_signup_disabled do
describe 'standard' do
it_behaves_like 'registration and login'
end
end
- context :manage, :orchestrated, :ldap, :skip_signup_disabled do
+ context 'Manage', :orchestrated, :ldap, :skip_signup_disabled do
describe 'while LDAP is enabled' do
it_behaves_like 'registration and login'
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index 53865b44684..7bf26c22fa6 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage do
+ context 'Manage' do
describe 'Add project member' do
it 'user adds project member' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index c8ea558aed6..a242f2158da 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :smoke do
+ context 'Manage', :smoke do
describe 'Project creation' do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
index d1cd9865aef..a99b0522e73 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :orchestrated, :github do
+ context 'Manage', :orchestrated, :github do
describe 'Project import from GitHub' do
let(:imported_project) do
Factory::Resource::ProjectImportedFromGithub.fabricate! do |project|
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index 97ac35e8dba..768d40f3acf 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage do
+ context 'Manage' do
describe 'Project activity' do
it 'user creates an event in the activity page upon Git push' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index 49d76f31e3a..e67561b3a39 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :plan, :smoke do
+ context 'Plan', :smoke do
describe 'Issue creation' do
let(:issue_title) { 'issue title' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
index f59bfb6b64d..037ff5efbd4 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Merge request creation' do
it 'user creates a new merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 922feadb4e1..058af8aebdd 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Merge request creation from fork' do
it 'user forks a project, submits a merge request and maintainer merges it' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
index 827dbb67076..3bcf086d332 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Merge request rebasing' do
it 'user rebases source branch of merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index b5b8855a35d..46e1005829d 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Merge request squashing' do
it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
index c7edcf4c025..7705e12b95e 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'File templates' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
index b163ca896a7..df70b9608d9 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'SSH keys support' do
let(:key_title) { "key for ssh tests #{Time.now.to_f}" }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
index a982a4604ac..9c64a9a3439 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Git clone over HTTP', :ldap do
let(:location) do
Page::Project::Show.act do
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index 82d635065a0..f65a1569fb0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Files management' do
it 'user creates, edits and deletes a file via the Web' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
index bf32569b6cb..b9bed39662f 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Git push over HTTP', :ldap do
it 'user pushes code to the repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index b2da685c477..5f42cb00bd3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Protected branch support', :ldap do
let(:branch_name) { 'protected-branch' }
let(:commit_message) { 'Protected push commit message' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
index 563393b3d07..36068ffba69 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'SSH key support' do
# Note: If you run this test against GDK make sure you've enabled sshd
# See: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index 1f07d08e664..07dbf39a8a3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Web IDE file templates' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
index 44dd85c1746..4126fd9fd3e 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Wiki management' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index e901531b1bf..d66bcce879b 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :verify, :orchestrated, :docker do
+ context 'Verify', :orchestrated, :docker do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index 8d83a20f5bf..5d9aa00582f 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :verify, :docker do
+ context 'Verify', :docker do
describe 'Runner registration' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
diff --git a/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb
index 08a87df5837..292f24d9c0d 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :verify do
+ context 'Verify' do
describe 'Secret variable support' do
it 'user adds a secret variable' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
index 17dfa887434..64b98da8bf5 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :release do
+ context 'Release' do
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 73af24e7f50..caf014c89ea 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,7 +3,7 @@
require 'digest/sha1'
module QA
- context :release, :docker do
+ context 'Release', :docker do
describe 'Git clone using a deploy key' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
index e521597e07f..263ba6a6800 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :release do
+ context 'Release' do
describe 'Deploy token creation' do
it 'user adds a deploy token' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 3735bc00aff..c98ede25b68 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -3,7 +3,7 @@
require 'pathname'
module QA
- context :configure, :orchestrated, :kubernetes do
+ context 'Configure', :orchestrated, :kubernetes do
describe 'Auto DevOps support' do
after do
@cluster&.remove!
diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
index af24b36b734..7096864e011 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :configure, :orchestrated, :mattermost do
+ context 'Configure', :orchestrated, :mattermost do
describe 'Mattermost support' do
it 'user creates a group with a mattermost team' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
new file mode 100644
index 00000000000..cf5cd3a79f8
--- /dev/null
+++ b/qa/qa/support/page/logging.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+module QA
+ module Support
+ module Page
+ module Logging
+ def refresh
+ log("refreshing #{current_url}")
+
+ super
+ end
+
+ def wait(max: 60, time: 0.1, reload: true)
+ log("with wait: max #{max}; time #{time}; reload #{reload}")
+ now = Time.now
+
+ element = super
+
+ log("ended wait after #{Time.now - now} seconds")
+
+ element
+ end
+
+ def scroll_to(selector, text: nil)
+ msg = "scrolling to :#{selector}"
+ msg += " with text: #{text}" if text
+ log(msg)
+
+ super
+ end
+
+ def asset_exists?(url)
+ exists = super
+
+ log("asset_exists? #{url} returned #{exists}")
+
+ exists
+ end
+
+ def find_element(name)
+ log("finding :#{name}")
+
+ element = super
+
+ log("found :#{name}") if element
+
+ element
+ end
+
+ def all_elements(name)
+ log("finding all :#{name}")
+
+ elements = super
+
+ log("found #{elements.size} :#{name}") if elements
+
+ elements
+ end
+
+ def click_element(name)
+ log("clicking :#{name}")
+
+ super
+ end
+
+ def fill_element(name, content)
+ masked_content = name.to_s.include?('password') ? '*****' : content
+
+ log(%Q(filling :#{name} with "#{masked_content}"))
+
+ super
+ end
+
+ def has_element?(name)
+ found = super
+
+ log("has_element? :#{name} returned #{found}")
+
+ found
+ end
+
+ def within_element(name)
+ log("within element :#{name}")
+
+ element = super
+
+ log("end within element :#{name}")
+
+ element
+ end
+
+ private
+
+ def log(msg)
+ QA::Runtime::Logger.debug(msg)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb
index 184802a7903..229f93a1041 100644
--- a/qa/spec/factory/base_spec.rb
+++ b/qa/spec/factory/base_spec.rb
@@ -35,8 +35,8 @@ describe QA::Factory::Base do
end
end
- it 'does not log the factory and build method when VERBOSE=false' do
- stub_env('VERBOSE', 'false')
+ it 'does not log the factory and build method when QA_DEBUG=false' do
+ stub_env('QA_DEBUG', 'false')
expect(factory).to receive(fabrication_method_used).and_return(product_location)
expect { subject.public_send(fabrication_method_called, 'something', factory: factory) }
@@ -79,8 +79,8 @@ describe QA::Factory::Base do
expect(result).to eq(product)
end
- it 'logs the factory and build method when VERBOSE=true' do
- stub_env('VERBOSE', 'true')
+ it 'logs the factory and build method when QA_DEBUG=true' do
+ stub_env('QA_DEBUG', 'true')
expect(factory).to receive(:fabricate_via_api!).and_return(product_location)
expect { subject.fabricate_via_api!(factory: factory, parents: []) }
@@ -106,8 +106,8 @@ describe QA::Factory::Base do
expect(result).to eq(product)
end
- it 'logs the factory and build method when VERBOSE=true' do
- stub_env('VERBOSE', 'true')
+ it 'logs the factory and build method when QA_DEBUG=true' do
+ stub_env('QA_DEBUG', 'true')
expect { subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) }
.to output(/==> Built a MyFactory via browser_ui with args \["something"\] in [\d\w\.\-]+/)
diff --git a/qa/spec/factory/repository/push_spec.rb b/qa/spec/factory/repository/push_spec.rb
new file mode 100644
index 00000000000..2eb6c008248
--- /dev/null
+++ b/qa/spec/factory/repository/push_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+describe QA::Factory::Repository::Push do
+ describe '.files=' do
+ let(:files) do
+ [
+ {
+ name: 'file.txt',
+ content: 'foo'
+ }
+ ]
+ end
+
+ it 'raises an error if files is not an array' do
+ expect { subject.files = '' }.to raise_error(ArgumentError)
+ end
+
+ it 'raises an error if files is an empty array' do
+ expect { subject.files = [] }.to raise_error(ArgumentError)
+ end
+
+ it 'does not raise if files is an array' do
+ expect { subject.files = files }.not_to raise_error
+ end
+ end
+end
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index 53bff3bf0b3..c629f802aa4 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -1,17 +1,18 @@
describe QA::Git::Repository do
+ include Support::StubENV
+
let(:repository) { described_class.new }
before do
+ stub_env('GITLAB_USERNAME', 'root')
cd_empty_temp_directory
set_bad_uri
repository.use_default_credentials
end
describe '#clone' do
- it 'redacts credentials from the URI in output' do
- output, _ = repository.clone
-
- expect(output).to include("fatal: unable to access 'http://****@foo/bar.git/'")
+ it 'is unable to resolve host' do
+ expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'")
end
end
@@ -20,10 +21,8 @@ describe QA::Git::Repository do
`git init` # need a repo to push from
end
- it 'redacts credentials from the URI in output' do
- output, _ = repository.push_changes
-
- expect(output).to include("error: failed to push some refs to 'http://****@foo/bar.git'")
+ it 'fails to push changes' do
+ expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'")
end
end
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
new file mode 100644
index 00000000000..9f17de4edbf
--- /dev/null
+++ b/qa/spec/page/logging_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'capybara/dsl'
+
+describe QA::Support::Page::Logging do
+ let(:page) { double().as_null_object }
+
+ before do
+ allow(Capybara).to receive(:current_session).and_return(page)
+ allow(page).to receive(:current_url).and_return('http://current-url')
+ allow(page).to receive(:has_css?).with(any_args).and_return(true)
+ end
+
+ subject do
+ Class.new(QA::Page::Base) do
+ prepend QA::Support::Page::Logging
+ end.new
+ end
+
+ it 'logs refresh' do
+ expect { subject.refresh }
+ .to output(%r{refreshing http://current-url}).to_stdout_from_any_process
+ end
+
+ it 'logs wait' do
+ expect { subject.wait(max: 0) {} }
+ .to output(/with wait/).to_stdout_from_any_process
+ expect { subject.wait(max: 0) {} }
+ .to output(/ended wait after .* seconds$/).to_stdout_from_any_process
+ end
+
+ it 'logs scroll_to' do
+ expect { subject.scroll_to(:element) }
+ .to output(/scrolling to :element/).to_stdout_from_any_process
+ end
+
+ it 'logs asset_exists?' do
+ expect { subject.asset_exists?('http://asset-url') }
+ .to output(%r{asset_exists\? http://asset-url returned false}).to_stdout_from_any_process
+ end
+
+ it 'logs find_element' do
+ expect { subject.find_element(:element) }
+ .to output(/found :element/).to_stdout_from_any_process
+ end
+
+ it 'logs click_element' do
+ expect { subject.click_element(:element) }
+ .to output(/clicking :element/).to_stdout_from_any_process
+ end
+
+ it 'logs fill_element' do
+ expect { subject.fill_element(:element, 'foo') }
+ .to output(/filling :element with "foo"/).to_stdout_from_any_process
+ end
+
+ it 'logs has_element?' do
+ expect { subject.has_element?(:element) }
+ .to output(/has_element\? :element returned true/).to_stdout_from_any_process
+ end
+
+ it 'logs within_element' do
+ expect { subject.within_element(:element) }
+ .to output(/within element :element/).to_stdout_from_any_process
+ expect { subject.within_element(:element) }
+ .to output(/end within element :element/).to_stdout_from_any_process
+ end
+
+ context 'all_elements' do
+ it 'logs the number of elements found' do
+ allow(page).to receive(:all).and_return([1, 2])
+
+ expect { subject.all_elements(:element) }
+ .to output(/finding all :element/).to_stdout_from_any_process
+ expect { subject.all_elements(:element) }
+ .to output(/found 2 :element/).to_stdout_from_any_process
+ end
+
+ it 'logs 0 if no elements are found' do
+ allow(page).to receive(:all).and_return([])
+
+ expect { subject.all_elements(:element) }
+ .to output(/finding all :element/).to_stdout_from_any_process
+ expect { subject.all_elements(:element) }
+ .not_to output(/found 0 :elements/).to_stdout_from_any_process
+ end
+ end
+end
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index b5ecf1afb80..c59c415c148 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::Env do
include Support::StubENV
@@ -34,14 +36,14 @@ describe QA::Runtime::Env do
end
end
- describe '.verbose?' do
- it_behaves_like 'boolean method', :verbose?, 'VERBOSE', false
- end
-
describe '.signup_disabled?' do
it_behaves_like 'boolean method', :signup_disabled?, 'SIGNUP_DISABLED', false
end
+ describe '.debug?' do
+ it_behaves_like 'boolean method', :debug?, 'QA_DEBUG', false
+ end
+
describe '.chrome_headless?' do
it_behaves_like 'boolean method', :chrome_headless?, 'CHROME_HEADLESS', true
end
@@ -166,4 +168,18 @@ describe QA::Runtime::Env do
expect { described_class.require_github_access_token! }.not_to raise_error
end
end
+
+ describe '.log_destination' do
+ it 'returns $stdout if QA_LOG_PATH is not defined' do
+ stub_env('QA_LOG_PATH', nil)
+
+ expect(described_class.log_destination).to eq($stdout)
+ end
+
+ it 'returns the path if QA_LOG_PATH is defined' do
+ stub_env('QA_LOG_PATH', 'path/to_file')
+
+ expect(described_class.log_destination).to eq('path/to_file')
+ end
+ end
end
diff --git a/qa/spec/runtime/logger_spec.rb b/qa/spec/runtime/logger_spec.rb
new file mode 100644
index 00000000000..794e1f9bfe6
--- /dev/null
+++ b/qa/spec/runtime/logger_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+describe QA::Runtime::Logger do
+ it 'logs debug' do
+ expect { described_class.debug('test') }.to output(/DEBUG -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs info' do
+ expect { described_class.info('test') }.to output(/INFO -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs warn' do
+ expect { described_class.warn('test') }.to output(/WARN -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs error' do
+ expect { described_class.error('test') }.to output(/ERROR -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs fatal' do
+ expect { described_class.fatal('test') }.to output(/FATAL -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs unknown' do
+ expect { described_class.unknown('test') }.to output(/ANY -- : test/).to_stdout_from_any_process
+ end
+end
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 8e6613cd688..8e01da01340 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -3,6 +3,10 @@ require_relative '../qa'
Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
RSpec.configure do |config|
+ config.before do |example|
+ QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
+ end
+
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
diff --git a/scripts/frontend/frontend_script_utils.js b/scripts/frontend/frontend_script_utils.js
index e42b912d359..e3d357b4a40 100644
--- a/scripts/frontend/frontend_script_utils.js
+++ b/scripts/frontend/frontend_script_utils.js
@@ -13,7 +13,8 @@ const execGitCmd = args =>
exec('git', args)
.trim()
.toString()
- .split('\n');
+ .split('\n')
+ .filter(Boolean);
module.exports = {
getStagedFiles: fileExtensionFilter => {
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
index b66ba885701..ce86a9f4601 100644
--- a/scripts/frontend/prettier.js
+++ b/scripts/frontend/prettier.js
@@ -1,126 +1,116 @@
const glob = require('glob');
const prettier = require('prettier');
const fs = require('fs');
-const path = require('path');
-const prettierIgnore = require('ignore')();
+const { getStagedFiles } = require('./frontend_script_utils');
-const getStagedFiles = require('./frontend_script_utils').getStagedFiles;
+const matchExtensions = ['js', 'vue'];
+
+// This will improve glob performance by excluding certain directories.
+// The .prettierignore file will also be respected, but after the glob has executed.
+const globIgnore = ['**/node_modules/**', 'vendor/**', 'public/**'];
+
+const readFileAsync = (file, options) =>
+ new Promise((resolve, reject) => {
+ fs.readFile(file, options, function(err, data) {
+ if (err) reject(err);
+ else resolve(data);
+ });
+ });
+
+const writeFileAsync = (file, data, options) =>
+ new Promise((resolve, reject) => {
+ fs.writeFile(file, data, options, function(err) {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
const mode = process.argv[2] || 'check';
const shouldSave = mode === 'save' || mode === 'save-all';
const allFiles = mode === 'check-all' || mode === 'save-all';
-let dirPath = process.argv[3] || '';
-if (dirPath && dirPath.charAt(dirPath.length - 1) !== '/') dirPath += '/';
-
-const config = {
- patterns: ['**/*.js', '**/*.vue', '**/*.scss'],
- /*
- * The ignore patterns below are just to reduce search time with glob, as it includes the
- * folders with the most ignored assets, the actual `.prettierignore` will be used later on
- */
- ignore: ['**/node_modules/**', '**/vendor/**', '**/public/**'],
- parsers: {
- js: 'babylon',
- vue: 'vue',
- scss: 'css',
- },
-};
+let globDir = process.argv[3] || '';
+if (globDir && globDir.charAt(globDir.length - 1) !== '/') globDir += '/';
-/*
- * Unfortunately the prettier API does not expose support for `.prettierignore` files, they however
- * use the ignore package, so we do the same. We simply cannot use the glob package, because
- * gitignore style is not compatible with globs ignore style.
- */
-prettierIgnore.add(
- fs
- .readFileSync(path.join(__dirname, '../../', '.prettierignore'))
- .toString()
- .trim()
- .split(/\r?\n/)
+console.log(
+ `Loading all ${allFiles ? '' : 'staged '}files ${globDir ? `within ${globDir} ` : ''}...`
);
-const availableExtensions = Object.keys(config.parsers);
-
-console.log(`Loading ${allFiles ? 'All' : 'Selected'} Files ...`);
+const globPatterns = matchExtensions.map(ext => `${globDir}**/*.${ext}`);
+const matchedFiles = allFiles
+ ? glob.sync(`{${globPatterns.join(',')}}`, { ignore: globIgnore })
+ : getStagedFiles(globPatterns);
+const matchedCount = matchedFiles.length;
-const stagedFiles =
- allFiles || dirPath ? null : getStagedFiles(availableExtensions.map(ext => `*.${ext}`));
-
-if (stagedFiles) {
- if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) {
- console.log('No matching staged files.');
- process.exit(1);
- }
- console.log(`Matching staged Files : ${stagedFiles.length}`);
+if (!matchedCount) {
+ console.log('No files found to process with prettier');
+ process.exit(0);
}
let didWarn = false;
-let didError = false;
-
-let files;
-if (allFiles) {
- const ignore = config.ignore;
- const patterns = config.patterns;
- const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
- files = glob.sync(globPattern, { ignore }).filter(f => allFiles || stagedFiles.includes(f));
-} else if (dirPath) {
- const ignore = config.ignore;
- const patterns = config.patterns.map(item => {
- return dirPath + item;
- });
- const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
- files = glob.sync(globPattern, { ignore });
-} else {
- files = stagedFiles.filter(f => availableExtensions.includes(f.split('.').pop()));
-}
-
-files = prettierIgnore.filter(files);
-
-if (!files.length) {
- console.log('No Files found to process with Prettier');
- process.exit(1);
-}
-
-console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`);
-
-files.forEach(file => {
- try {
- prettier
- .resolveConfig(file)
- .then(options => {
- const fileExtension = file.split('.').pop();
- Object.assign(options, {
- parser: config.parsers[fileExtension],
+let passedCount = 0;
+let failedCount = 0;
+let ignoredCount = 0;
+
+console.log(`${shouldSave ? 'Updating' : 'Checking'} ${matchedCount} file(s)`);
+
+const fixCommand = `yarn prettier-${allFiles ? 'all' : 'staged'}-save`;
+const warningMessage = `
+===============================
+GitLab uses Prettier to format all JavaScript code.
+Please format each file listed below or run "${fixCommand}"
+===============================
+`;
+
+const checkFileWithOptions = (filePath, options) =>
+ readFileAsync(filePath, 'utf8').then(input => {
+ if (shouldSave) {
+ const output = prettier.format(input, options);
+ if (input === output) {
+ passedCount += 1;
+ } else {
+ return writeFileAsync(filePath, output, 'utf8').then(() => {
+ console.log(`Prettified : ${filePath}`);
+ failedCount += 1;
});
-
- const input = fs.readFileSync(file, 'utf8');
-
- if (shouldSave) {
- const output = prettier.format(input, options);
- if (output !== input) {
- fs.writeFileSync(file, output, 'utf8');
- console.log(`Prettified : ${file}`);
- }
- } else if (!prettier.check(input, options)) {
- if (!didWarn) {
- console.log(
- '\n===============================\nGitLab uses Prettier to format all JavaScript code.\nPlease format each file listed below or run "yarn prettier-staged-save"\n===============================\n'
- );
- didWarn = true;
- }
- console.log(`Prettify Manually : ${file}`);
+ }
+ } else {
+ if (prettier.check(input, options)) {
+ passedCount += 1;
+ } else {
+ if (!didWarn) {
+ console.log(warningMessage);
+ didWarn = true;
}
- })
- .catch(e => {
- console.log(`Error on loading the Config File: ${e.message}`);
- process.exit(1);
- });
- } catch (error) {
- didError = true;
- console.log(`\n\nError with ${file}: ${error.message}`);
- }
-});
+ console.log(`Prettify Manually : ${filePath}`);
+ failedCount += 1;
+ }
+ }
+ });
-if (didWarn || didError) {
- process.exit(1);
-}
+const checkFileWithPrettierConfig = filePath =>
+ prettier
+ .getFileInfo(filePath, { ignorePath: '.prettierignore' })
+ .then(({ ignored, inferredParser }) => {
+ if (ignored || !inferredParser) {
+ ignoredCount += 1;
+ return;
+ }
+ return prettier.resolveConfig(filePath).then(fileOptions => {
+ const options = { ...fileOptions, parser: inferredParser };
+ return checkFileWithOptions(filePath, options);
+ });
+ });
+
+Promise.all(matchedFiles.map(checkFileWithPrettierConfig))
+ .then(() => {
+ const failAction = shouldSave ? 'fixed' : 'failed';
+ console.log(
+ `\nSummary:\n ${matchedCount} files processed (${passedCount} passed, ${failedCount} ${failAction}, ${ignoredCount} ignored)\n`
+ );
+
+ if (didWarn) process.exit(1);
+ })
+ .catch(e => {
+ console.log(`\nAn error occured while processing files with prettier: ${e.message}\n`);
+ process.exit(1);
+ });
diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb
index a5f0ec372d8..4166070f7cd 100755
--- a/scripts/review_apps/automated_cleanup.rb
+++ b/scripts/review_apps/automated_cleanup.rb
@@ -5,12 +5,26 @@ require_relative File.expand_path('../../lib/quality/helm_client.rb', __dir__)
require_relative File.expand_path('../../lib/quality/kubernetes_client.rb', __dir__)
class AutomatedCleanup
- attr_reader :project_path, :gitlab_token, :cleaned_up_releases
+ attr_reader :project_path, :gitlab_token
+
+ DEPLOYMENTS_PER_PAGE = 100
+ HELM_RELEASES_BATCH_SIZE = 5
+ IGNORED_HELM_ERRORS = [
+ 'transport is closing',
+ 'error upgrading connection'
+ ].freeze
+ IGNORED_KUBERNETES_ERRORS = [
+ 'NotFound'
+ ].freeze
+
+ def self.ee?
+ ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('CHANGELOG-EE.md')
+ end
def initialize(project_path: ENV['CI_PROJECT_PATH'], gitlab_token: ENV['GITLAB_BOT_REVIEW_APPS_CLEANUP_TOKEN'])
@project_path = project_path
@gitlab_token = gitlab_token
- @cleaned_up_releases = []
+ ENV['TILLER_NAMESPACE'] ||= review_apps_namespace
end
def gitlab
@@ -25,12 +39,16 @@ class AutomatedCleanup
end
end
+ def review_apps_namespace
+ self.class.ee? ? 'review-apps-ee' : 'review-apps-ce'
+ end
+
def helm
- @helm ||= Quality::HelmClient.new
+ @helm ||= Quality::HelmClient.new(namespace: review_apps_namespace)
end
def kubernetes
- @kubernetes ||= Quality::KubernetesClient.new
+ @kubernetes ||= Quality::KubernetesClient.new(namespace: review_apps_namespace)
end
def perform_gitlab_environment_cleanup!(days_for_stop:, days_for_delete:)
@@ -39,26 +57,27 @@ class AutomatedCleanup
checked_environments = []
delete_threshold = threshold_time(days: days_for_delete)
stop_threshold = threshold_time(days: days_for_stop)
- gitlab.deployments(project_path, per_page: 50).auto_paginate do |deployment|
- next unless deployment.environment.name.start_with?('review/')
- next if checked_environments.include?(deployment.environment.slug)
- puts
+ gitlab.deployments(project_path, per_page: DEPLOYMENTS_PER_PAGE).auto_paginate do |deployment|
+ environment = deployment.environment
- checked_environments << deployment.environment.slug
- deployed_at = Time.parse(deployment.created_at)
+ next unless environment.name.start_with?('review/')
+ next if checked_environments.include?(environment.slug)
+
+ last_deploy = deployment.created_at
+ deployed_at = Time.parse(last_deploy)
if deployed_at < delete_threshold
- print_release_state(subject: 'Review app', release_name: deployment.environment.slug, release_date: deployment.created_at, action: 'deleting')
- gitlab.delete_environment(project_path, deployment.environment.id)
- cleaned_up_releases << deployment.environment.slug
+ delete_environment(environment, deployment)
+ release = Quality::HelmClient::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace)
+ delete_helm_release(release)
elsif deployed_at < stop_threshold
- print_release_state(subject: 'Review app', release_name: deployment.environment.slug, release_date: deployment.created_at, action: 'stopping')
- gitlab.stop_environment(project_path, deployment.environment.id)
- cleaned_up_releases << deployment.environment.slug
+ stop_environment(environment, deployment)
else
- print_release_state(subject: 'Review app', release_name: deployment.environment.slug, release_date: deployment.created_at, action: 'leaving')
+ print_release_state(subject: 'Review app', release_name: environment.slug, release_date: last_deploy, action: 'leaving')
end
+
+ checked_environments << environment.slug
end
end
@@ -66,25 +85,58 @@ class AutomatedCleanup
puts "Checking for Helm releases not updated in the last #{days} days..."
threshold_day = threshold_time(days: days)
- helm.releases(args: ['--deployed', '--failed', '--date', '--reverse', '--max 25']).each do |release|
- next if cleaned_up_releases.include?(release.name)
- if release.last_update < threshold_day
- print_release_state(subject: 'Release', release_name: release.name, release_date: release.last_update, action: 'cleaning')
- helm.delete(release_name: release.name)
- kubernetes.cleanup(release_name: release.name)
+ helm_releases.each do |release|
+ if release.status == 'FAILED' || release.last_update < threshold_day
+ delete_helm_release(release)
else
print_release_state(subject: 'Release', release_name: release.name, release_date: release.last_update, action: 'leaving')
end
end
end
+ private
+
+ def delete_environment(environment, deployment)
+ print_release_state(subject: 'Review app', release_name: environment.slug, release_date: deployment.created_at, action: 'deleting')
+ gitlab.delete_environment(project_path, environment.id)
+ end
+
+ def stop_environment(environment, deployment)
+ print_release_state(subject: 'Review app', release_name: environment.slug, release_date: deployment.created_at, action: 'stopping')
+ gitlab.stop_environment(project_path, environment.id)
+ end
+
+ def helm_releases
+ args = ['--all', '--date', "--max #{HELM_RELEASES_BATCH_SIZE}"]
+
+ helm.releases(args: args)
+ end
+
+ def delete_helm_release(release)
+ print_release_state(subject: 'Release', release_name: release.name, release_status: release.status, release_date: release.last_update, action: 'cleaning')
+ helm.delete(release_name: release.name)
+ kubernetes.cleanup(release_name: release.name)
+ rescue Quality::HelmClient::CommandFailedError => ex
+ raise ex unless ignore_exception?(ex.message, IGNORED_HELM_ERRORS)
+
+ puts "Ignoring the following Helm error:\n#{ex}\n"
+ rescue Quality::KubernetesClient::CommandFailedError => ex
+ raise ex unless ignore_exception?(ex.message, IGNORED_KUBERNETES_ERRORS)
+
+ puts "Ignoring the following Kubernetes error:\n#{ex}\n"
+ end
+
def threshold_time(days:)
Time.now - days * 24 * 3600
end
- def print_release_state(subject:, release_name:, release_date:, action:)
- puts "\n#{subject} '#{release_name}' was last deployed on #{release_date}: #{action} it."
+ def ignore_exception?(exception_message, exceptions_ignored)
+ exception_message.match?(/(#{exceptions_ignored})/)
+ end
+
+ def print_release_state(subject:, release_name:, release_date:, action:, release_status: nil)
+ puts "\n#{subject} '#{release_name}' #{"(#{release_status}) " if release_status}was last deployed on #{release_date}: #{action} it.\n"
end
end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 78293464265..d372bcbdab1 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -47,15 +47,23 @@ function create_secret() {
--dry-run -o json | kubectl apply -f -
}
+function deployExists() {
+ local namespace="${1}"
+ local deploy="${2}"
+ helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1
+ return $?
+}
+
function previousDeployFailed() {
set +e
- echo "Checking for previous deployment of $CI_ENVIRONMENT_SLUG"
- deployment_status=$(helm status $CI_ENVIRONMENT_SLUG >/dev/null 2>&1)
+ deploy="${1}"
+ echo "Checking for previous deployment of ${deploy}"
+ deployment_status=$(helm status ${deploy} >/dev/null 2>&1)
status=$?
# if `status` is `0`, deployment exists, has a status
if [ $status -eq 0 ]; then
echo "Previous deployment found, checking status"
- deployment_status=$(helm status $CI_ENVIRONMENT_SLUG | grep ^STATUS | cut -d' ' -f2)
+ deployment_status=$(helm status ${deploy} | grep ^STATUS | cut -d' ' -f2)
echo "Previous deployment state: $deployment_status"
if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then
status=0;
@@ -113,7 +121,7 @@ function deploy() {
fi
# Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade`
- if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previousDeployFailed ; then
+ if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previousDeployFailed "$CI_ENVIRONMENT_SLUG" ; then
echo "Deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG"
delete
cleanup
@@ -149,6 +157,7 @@ HELM_CMD=$(cat << EOF
--set gitlab.gitlab-shell.image.tag="v$GITLAB_SHELL_VERSION" \
--set gitlab.unicorn.workhorse.image="$gitlab_workhorse_image_repository" \
--set gitlab.unicorn.workhorse.tag="$CI_COMMIT_REF_NAME" \
+ --set nginx-ingress.controller.config.ssl-ciphers="ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4" \
--namespace="$KUBE_NAMESPACE" \
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \
"$name" \
@@ -182,3 +191,23 @@ function cleanup() {
| xargs kubectl -n "$KUBE_NAMESPACE" delete \
|| true
}
+
+function install_external_dns() {
+ local release_name="dns-gitlab-review-app"
+ local domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}')
+
+ if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then
+ echo "Installing external-dns helm chart"
+ helm repo update
+ helm install stable/external-dns \
+ -n "${release_name}" \
+ --namespace "${KUBE_NAMESPACE}" \
+ --set provider="aws" \
+ --set aws.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \
+ --set aws.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \
+ --set aws.zoneType="public" \
+ --set domainFilters[0]="${domain}" \
+ --set txtOwnerId="${KUBE_NAMESPACE}" \
+ --set rbac.create="true"
+ fi
+}
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index d98e6ff0df8..c365988a100 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -30,6 +30,15 @@ describe Boards::IssuesController do
context 'when list id is present' do
context 'with valid list id' do
+ let(:group) { create(:group, :private, projects: [project]) }
+ let(:group_board) { create(:board, group: group) }
+ let!(:list3) { create(:list, board: group_board, label: development, position: 2) }
+ let(:sub_group_1) { create(:group, :private, parent: group) }
+
+ before do
+ group.add_maintainer(user)
+ end
+
it 'returns issues that have the list label applied' do
issue = create(:labeled_issue, project: project, labels: [planning])
create(:labeled_issue, project: project, labels: [planning])
@@ -56,6 +65,39 @@ describe Boards::IssuesController do
expect { list_issues(user: user, board: board, list: list2) }.not_to exceed_query_limit(control_count)
end
+
+ it 'avoids N+1 database queries when adding a project', :request_store do
+ create(:labeled_issue, project: project, labels: [development])
+ control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count
+
+ 2.times do
+ p = create(:project, group: group)
+ create(:labeled_issue, project: p, labels: [development])
+ end
+
+ project_2 = create(:project, group: group)
+ create(:labeled_issue, project: project_2, labels: [development], assignees: [johndoe])
+
+ # because each issue without relative_position must be updated with
+ # a different value, we have 8 extra queries per issue
+ expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1))
+ end
+
+ it 'avoids N+1 database queries when adding a subgroup, project, and issue', :nested_groups do
+ create(:project, group: sub_group_1)
+ create(:labeled_issue, project: project, labels: [development])
+ control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count
+ project_2 = create(:project, group: group)
+
+ 2.times do
+ p = create(:project, group: sub_group_1)
+ create(:labeled_issue, project: p, labels: [development])
+ end
+
+ create(:labeled_issue, project: project_2, labels: [development], assignees: [johndoe])
+
+ expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1))
+ end
end
context 'with invalid list id' do
@@ -102,12 +144,15 @@ describe Boards::IssuesController do
sign_in(user)
params = {
- namespace_id: project.namespace.to_param,
- project_id: project,
board_id: board.to_param,
list_id: list.try(:to_param)
}
+ unless board.try(:parent)&.is_a?(Group)
+ params[:namespace_id] = project.namespace.to_param
+ params[:project_id] = project
+ end
+
get :index, params.compact
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 9df77560320..80138183c07 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1028,6 +1028,13 @@ describe Projects::IssuesController do
.not_to exceed_query_limit(control)
end
+ context 'when user is setting notes filters' do
+ let(:issuable) { issue }
+ let!(:discussion_note) { create(:discussion_note_on_issue, :system, noteable: issuable, project: project) }
+
+ it_behaves_like 'issuable notes filter'
+ end
+
context 'with cross-reference system note', :request_store do
let(:new_issue) { create(:issue) }
let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" }
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 73b62dc1151..dcfd6c05200 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -87,6 +87,14 @@ describe Projects::MergeRequestsController do
end
end
+ context 'when user is setting notes filters' do
+ let(:issuable) { merge_request }
+ let!(:discussion_note) { create(:discussion_note_on_merge_request, :system, noteable: issuable, project: project) }
+ let!(:discussion_comment) { create(:discussion_note_on_merge_request, noteable: issuable, project: project) }
+
+ it_behaves_like 'issuable notes filter'
+ end
+
describe 'as json' do
context 'with basic serializer param' do
it 'renders basic MR entity as json' do
@@ -838,12 +846,14 @@ describe Projects::MergeRequestsController do
end
context 'with a forked project' do
- let(:fork_project) { create(:project, :repository, forked_from_project: project) }
- let(:fork_owner) { fork_project.owner }
+ let(:forked_project) { fork_project(project, fork_owner, repository: true) }
+ let(:fork_owner) { create(:user) }
before do
- merge_request.update!(source_project: fork_project)
- fork_project.add_reporter(user)
+ project.add_developer(fork_owner)
+
+ merge_request.update!(source_project: forked_project)
+ forked_project.add_reporter(user)
end
context 'user cannot push to source branch' do
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index e48c9dea976..9ac7b8ee8a8 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -47,6 +47,37 @@ describe Projects::NotesController do
get :index, request_params
end
+ context 'when user notes_filter is present' do
+ let(:notes_json) { parsed_response[:notes] }
+ let!(:comment) { create(:note, noteable: issue, project: project) }
+ let!(:system_note) { create(:note, noteable: issue, project: project, system: true) }
+
+ it 'filters system notes by comments' do
+ user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issue)
+
+ get :index, request_params
+
+ expect(notes_json.count).to eq(1)
+ expect(notes_json.first[:id].to_i).to eq(comment.id)
+ end
+
+ it 'returns all notes' do
+ user.set_notes_filter(UserPreference::NOTES_FILTERS[:all_notes], issue)
+
+ get :index, request_params
+
+ expect(notes_json.map { |note| note[:id].to_i }).to contain_exactly(comment.id, system_note.id)
+ end
+
+ it 'does not merge label event notes' do
+ user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issue)
+
+ expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new)
+
+ get :index, request_params
+ end
+ end
+
context 'for a discussion note' do
let(:project) { create(:project, :repository) }
let!(:note) { create(:discussion_note_on_merge_request, project: project) }
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index e0aeadeecd7..2c76c22ba69 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -119,11 +119,11 @@ FactoryBot.define do
trait :codequality do
file_type :codequality
- file_format :gzip
+ file_format :raw
after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload(
- Rails.root.join('spec/fixtures/codequality/codequality.json.gz'), 'application/x-gzip')
+ Rails.root.join('spec/fixtures/codequality/codequality.json'), 'application/json')
end
end
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index f564e7bee47..24e70913b87 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -47,5 +47,15 @@ FactoryBot.define do
trait :ref_protected do
access_level :ref_protected
end
+
+ trait :tagged_only do
+ run_untagged false
+
+ tag_list %w(tag1 tag2)
+ end
+
+ trait :locked do
+ locked true
+ end
end
end
diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb
new file mode 100644
index 00000000000..6fdada75a3d
--- /dev/null
+++ b/spec/factories/clusters/kubernetes_namespaces.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do
+ cluster
+ project
+ cluster_project
+ end
+end
diff --git a/spec/factories/clusters/projects.rb b/spec/factories/clusters/projects.rb
new file mode 100644
index 00000000000..6cda77c6f85
--- /dev/null
+++ b/spec/factories/clusters/projects.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :cluster_project, class: Clusters::Project do
+ cluster
+ project
+ end
+end
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index cac56695319..90d6a338479 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -16,5 +16,10 @@ FactoryBot.define do
allow(deployment.project.repository).to receive(:create_ref)
end
end
+
+ trait :review_app do
+ sha { TestEnv::BRANCH_SHA['pages-deploy'] }
+ ref 'pages-deploy'
+ end
end
end
diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb
deleted file mode 100644
index bc59fea81ec..00000000000
--- a/spec/factories/forked_project_links.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-FactoryBot.define do
- factory :forked_project_link do
- association :forked_to_project, factory: [:project, :repository]
- association :forked_from_project, factory: [:project, :repository]
-
- after(:create) do |link|
- link.forked_from_project.reload
- link.forked_to_project.reload
- end
-
- trait :forked_to_empty_project do
- association :forked_to_project, factory: [:project, :repository]
- end
- end
-end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 8094c43b065..2392bfc4a53 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -101,6 +101,20 @@ FactoryBot.define do
end
end
+ trait :deployed_review_app do
+ target_branch 'pages-deploy-target'
+
+ transient do
+ deployment { create(:deployment, :review_app) }
+ end
+
+ after(:build) do |merge_request, evaluator|
+ merge_request.source_branch = evaluator.deployment.ref
+ merge_request.source_project = evaluator.deployment.project
+ merge_request.target_project = evaluator.deployment.project
+ end
+ end
+
after(:build) do |merge_request|
target_project = merge_request.target_project
source_project = merge_request.source_project
diff --git a/spec/factories/user_preferences.rb b/spec/factories/user_preferences.rb
new file mode 100644
index 00000000000..19059a93625
--- /dev/null
+++ b/spec/factories/user_preferences.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :user_preference do
+ user
+
+ trait :only_comments do
+ issue_notes_filter { UserPreference::NOTES_FILTERS[:only_comments] }
+ merge_request_notes_filter { UserPreference::NOTE_FILTERS[:only_comments] }
+ end
+ end
+end
diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb
new file mode 100644
index 00000000000..a6ca0803469
--- /dev/null
+++ b/spec/features/admin/dashboard_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe 'admin visits dashboard' do
+ include ProjectForksHelper
+
+ before do
+ sign_in(create(:admin))
+ end
+
+ context 'counting forks' do
+ it 'correctly counts 2 forks of a project' do
+ project = create(:project)
+ project_fork = fork_project(project)
+ fork_project(project_fork)
+
+ # Make sure the fork_networks & fork_networks reltuples have been updated
+ # to get a correct count on postgresql
+ if Gitlab::Database.postgresql?
+ ActiveRecord::Base.connection.execute('ANALYZE fork_networks')
+ ActiveRecord::Base.connection.execute('ANALYZE fork_network_members')
+ end
+
+ visit admin_root_path
+
+ expect(page).to have_content('Forks 2')
+ end
+ end
+end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index e5c5ab9c039..31a1dcf826d 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -2,8 +2,12 @@ require 'spec_helper'
describe 'Dashboard shortcuts', :js do
context 'logged in' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
before do
- sign_in(create(:user))
+ project.add_developer(user)
+ sign_in(user)
visit root_dashboard_path
end
@@ -50,6 +54,6 @@ describe 'Dashboard shortcuts', :js do
end
def check_page_title(title)
- expect(find('.breadcrumbs-sub-title')).to have_content(title)
+ expect(find('.page-title')).to have_content(title)
end
end
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index 80df0618a6a..e8ca6a6714f 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -23,17 +23,17 @@ describe 'Group milestones' do
description.native.send_keys('')
- click_link('Preview')
+ click_button('Preview')
preview = find('.js-md-preview')
expect(preview).to have_content('Nothing to preview.')
- click_link('Write')
+ click_button('Write')
description.native.send_keys(':+1: Nice')
- click_link('Preview')
+ click_button('Preview')
expect(preview).to have_css('gl-emoji')
expect(find('#milestone_description', visible: false)).not_to be_visible
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index f76d30056da..ef5801e61e8 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -189,13 +189,21 @@ describe 'Dropdown milestone', :js do
end
it 'selects `no milestone`' do
- click_static_milestone('No Milestone')
+ click_static_milestone('None')
expect(page).to have_css(js_dropdown_milestone, visible: false)
expect_tokens([milestone_token('none', false)])
expect_filtered_search_input_empty
end
+ it 'selects `any milestone`' do
+ click_static_milestone('Any')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect_tokens([milestone_token('any', false)])
+ expect_filtered_search_input_empty
+ end
+
it 'selects `upcoming milestone`' do
click_static_milestone('Upcoming')
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 5e8662100c5..687a6f1eafc 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -47,15 +47,15 @@ describe "User creates issue" do
textarea = first(".gfm-form textarea")
page.within(form) do
- click_link("Preview")
+ click_button("Preview")
preview = find(".js-md-preview") # this element is findable only when the "Preview" link is clicked.
expect(preview).to have_content("Nothing to preview.")
- click_link("Write")
+ click_button("Write")
fill_in("Description", with: "Bug fixed :smile:")
- click_link("Preview")
+ click_button("Preview")
expect(preview).to have_css("gl-emoji")
expect(textarea).not_to be_visible
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index 1d9c3abc20f..60b88ef4bdf 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -17,9 +17,9 @@ describe "User edits issue", :js do
page.within(form) do
fill_in("Description", with: "Bug fixed :smile:")
- click_link("Preview")
+ click_button("Preview")
end
- expect(form).to have_link("Write")
+ expect(form).to have_button("Write")
end
end
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
index 1ac31de62cb..9d2a94a4a41 100644
--- a/spec/features/merge_request/user_creates_mr_spec.rb
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -28,4 +28,29 @@ describe 'Merge request > User creates MR' do
it_behaves_like 'a creatable merge request'
end
end
+
+ context 'source project', :js do
+ let(:user) { create(:user) }
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:source_project) { target_project }
+
+ before do
+ source_project.add_maintainer(user)
+
+ 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
+ })
+ end
+
+ it 'filters source project' do
+ find('.js-source-project').click
+ find('.dropdown-source-project input').set('source')
+
+ expect(find('.dropdown-source-project .dropdown-content')).not_to have_content(source_project.name)
+ end
+ end
end
diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb
index 8c9e782aa76..3152707136c 100644
--- a/spec/features/merge_request/user_edits_mr_spec.rb
+++ b/spec/features/merge_request/user_edits_mr_spec.rb
@@ -1,11 +1,13 @@
require 'rails_helper'
describe 'Merge request > User edits MR' do
+ include ProjectForksHelper
+
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) }
+ let(:source_project) { fork_project(target_project, nil, repository: true) }
end
end
end
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
index 029b66b5e8e..fc4a188d4a7 100644
--- 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
@@ -1,18 +1,20 @@
require 'rails_helper'
describe 'Merge request > User sees MR from deleted forked project', :js do
+ include ProjectForksHelper
+
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
- let(:fork_project) { create(:project, :public, :repository, forked_from_project: project) }
+ let(:forked_project) { fork_project(project, nil, repository: true) }
let!(:merge_request) do
- create(:merge_request_with_diffs, source_project: fork_project,
+ create(:merge_request_with_diffs, source_project: forked_project,
target_project: project,
description: 'Test merge request')
end
before do
MergeRequests::MergeService.new(project, user).execute(merge_request)
- fork_project.destroy!
+ forked_project.destroy!
sign_in(user)
visit project_merge_request_path(project, merge_request)
end
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
index d4ad0b0a377..a6118453540 100644
--- 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
@@ -1,18 +1,20 @@
require 'rails_helper'
describe 'Merge request > User sees notes from forked project', :js do
+ include ProjectForksHelper
+
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
- let(:fork_project) { create(:project, :public, :repository, forked_from_project: project) }
+ let(:forked_project) { fork_project(project, nil, repository: true) }
let!(:merge_request) do
- create(:merge_request_with_diffs, source_project: fork_project,
+ create(:merge_request_with_diffs, source_project: forked_project,
target_project: project,
description: 'Test merge request')
end
before do
create(:note_on_commit, note: 'A commit comment',
- project: fork_project,
+ project: forked_project,
commit_id: merge_request.commit_shas.first)
sign_in(user)
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
index d30dcefc6aa..97093bbc2f6 100644
--- 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
@@ -1,17 +1,19 @@
require 'rails_helper'
describe 'Merge request > User sees pipelines from forked project', :js do
+ include ProjectForksHelper
+
let(:target_project) { create(:project, :public, :repository) }
let(:user) { target_project.creator }
- let(:fork_project) { create(:project, :repository, forked_from_project: target_project) }
+ let(:forked_project) { fork_project(target_project, nil, repository: true) }
let!(:merge_request) do
- create(:merge_request_with_diffs, source_project: fork_project,
+ create(:merge_request_with_diffs, source_project: forked_project,
target_project: target_project,
description: 'Test merge request')
end
let(:pipeline) do
create(:ci_pipeline,
- project: fork_project,
+ project: forked_project,
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch)
end
diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb
index 6ac495aa03d..71022c6bb08 100644
--- a/spec/features/merge_request/user_views_open_merge_request_spec.rb
+++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb
@@ -41,7 +41,7 @@ describe 'User views an open merge request' do
find('.gfm-form').fill_in(:merge_request_description, with: '')
page.within('.gfm-form') do
- click_link('Preview')
+ click_button('Preview')
expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
@@ -51,12 +51,12 @@ describe 'User views an open merge request' do
find('.gfm-form').fill_in(:merge_request_description, with: ':+1: Nice')
page.within('.gfm-form') do
- click_link('Preview')
+ click_button('Preview')
expect(find('.js-md-preview')).to have_css('gl-emoji')
end
- expect(find('.gfm-form')).to have_css('.js-md-preview').and have_link('Write')
+ expect(find('.gfm-form')).to have_css('.js-md-preview').and have_button('Write')
expect(find('#merge_request_description', visible: false)).not_to be_visible
end
end
diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
index 6397df086a7..29442a58ea4 100644
--- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb
+++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
@@ -28,19 +28,19 @@ describe "User adds a comment on a commit", :js do
fill_in("note[note]", with: "#{comment_text} #{emoji}")
# Check on `Preview` tab
- click_link("Preview")
+ click_button("Preview")
expect(find(".js-md-preview")).to have_content(comment_text).and have_css("gl-emoji")
expect(page).not_to have_css(".js-note-text")
# Check on the `Write` tab
- click_link("Write")
+ click_button("Write")
expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji}")
# Submit comment from the `Preview` tab to get rid of a separate `it` block
# which would specially tests if everything gets cleared from the note form.
- click_link("Preview")
+ click_button("Preview")
click_button("Comment")
end
@@ -88,13 +88,13 @@ describe "User adds a comment on a commit", :js do
# Test Preview feature for both forms.
page.within("form[data-line-code='#{sample_commit.line_code}']") do
- click_link("Preview")
+ click_button("Preview")
end
page.within("form[data-line-code='#{sample_commit.del_line_code}']") do
fill_in("note[note]", with: another_comment_text)
- click_link("Preview")
+ click_button("Preview")
end
expect(page).to have_css(".js-md-preview", visible: true, count: 2)
diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb
index 6397a8ad845..73ce8d2b996 100644
--- a/spec/features/projects/commit/user_comments_on_commit_spec.rb
+++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb
@@ -25,19 +25,19 @@ describe "User comments on commit", :js do
fill_in("note[note]", with: "#{comment_text} #{emoji_code}")
# Check on `Preview` tab
- click_link("Preview")
+ click_button("Preview")
expect(find(".js-md-preview")).to have_content(comment_text).and have_css("gl-emoji")
expect(page).not_to have_css(".js-note-text")
# Check on `Write` tab
- click_link("Write")
+ click_button("Write")
expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji_code}")
# Submit comment from the `Preview` tab to get rid of a separate `it` block
# which would specially tests if everything gets cleared from the note form.
- click_link("Preview")
+ click_button("Preview")
click_button("Comment")
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 7f5c3ff6ed8..c3902ecdd17 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -151,9 +151,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
it 'renders escaped tooltip name' do
- page.within('aside.right-sidebar') do
- expect(find('.active.build-job a')['data-original-title']).to eq('&lt;img src=x onerror=alert(document.domain)&gt; - passed')
- end
+ page.find('.active.build-job a').hover
+ expect(page).to have_content('<img src=x onerror=alert(document.domain)> - passed')
end
end
@@ -373,17 +372,15 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context 'when job starts environment', :js do
let(:environment) { create(:environment, name: 'production', project: project) }
+ before do
+ visit project_job_path(project, build)
+ wait_for_requests
+ end
+
context 'job is successful and has deployment' do
let(:build) { create(:ci_build, :success, :trace_live, environment: environment.name, pipeline: pipeline) }
let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) }
- before do
- visit project_job_path(project, build)
- wait_for_requests
- # scroll to the top of the page first
- execute_script "window.scrollTo(0,0)"
- end
-
it 'shows a link for the job' do
expect(page).to have_link environment.name
end
@@ -398,11 +395,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
let(:build) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) }
it 'shows a link for the job' do
- visit project_job_path(project, build)
- wait_for_requests
- # scroll to the top of the page first
- execute_script "window.scrollTo(0,0)"
-
expect(page).to have_link environment.name
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
@@ -412,11 +404,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do
- visit project_job_path(project, build)
- wait_for_all_requests
- # scroll to the top of the page first
- execute_script "window.scrollTo(0,0)"
-
expect(page).to have_link environment.name
expect(page).to have_content 'This job is creating a deployment'
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
@@ -453,8 +440,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
before do
visit project_job_path(project, job)
wait_for_requests
- # scroll to the top of the page first
- execute_script "window.scrollTo(0,0)"
end
context 'job with outdated deployment' do
@@ -484,8 +469,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'shows deployment message' do
expected_text = 'The deployment of this job to staging did not succeed.'
- expect(page).to have_css(
- '.environment-information', text: expected_text)
+ expect(page).to have_css('.environment-information', text: expected_text)
end
end
@@ -498,8 +482,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'shows deployment message' do
expected_text = 'This job is creating a deployment to staging'
- expect(page).to have_css(
- '.environment-information', text: expected_text)
+ expect(page).to have_css('.environment-information', text: expected_text)
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
@@ -509,10 +492,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'shows that deployment will be overwritten' do
expected_text = 'This job is creating a deployment to staging'
- expect(page).to have_css(
- '.environment-information', text: expected_text)
- expect(page).to have_css(
- '.environment-information', text: 'latest deployment')
+ expect(page).to have_css('.environment-information', text: expected_text)
+ expect(page).to have_css('.environment-information', text: 'latest deployment')
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
end
@@ -594,7 +575,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
it 'shows delayed job', :js do
- expect(page).to have_content('This is a scheduled to run in')
+ expect(page).to have_content('This is a delayed to run in')
expect(page).to have_content("This job will automatically run after it's timer finishes.")
expect(page).to have_link('Unschedule job')
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 491c64fc329..cd6c37bf54d 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -68,6 +68,10 @@ describe 'Pipeline', :js do
expect(page).to have_css('#js-tab-pipeline.active')
end
+ it 'shows link to the pipeline ref' do
+ expect(page).to have_link(pipeline.ref)
+ end
+
it_behaves_like 'showing user status' do
let(:user_with_status) { pipeline.user }
@@ -236,6 +240,20 @@ describe 'Pipeline', :js do
it { expect(page).not_to have_content('Cancel running') }
end
end
+
+ context 'when pipeline ref does not exist in repository anymore' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project,
+ ref: 'non-existent',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ it 'does not render link to the pipeline ref' do
+ expect(page).not_to have_link(pipeline.ref)
+ expect(page).to have_content(pipeline.ref)
+ end
+ end
end
context 'when user does not have access to read jobs' do
diff --git a/spec/finders/applications_finder_spec.rb b/spec/finders/applications_finder_spec.rb
new file mode 100644
index 00000000000..14d6b35cc27
--- /dev/null
+++ b/spec/finders/applications_finder_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ApplicationsFinder do
+ let(:application1) { create(:application, name: 'some_application', owner: nil, redirect_uri: 'http://some_application.url', scopes: '') }
+ let(:application2) { create(:application, name: 'another_application', owner: nil, redirect_uri: 'http://other_application.url', scopes: '') }
+
+ describe '#execute' do
+ it 'returns an array of applications' do
+ found = described_class.new.execute
+
+ expect(found).to match_array([application1, application2])
+ end
+ it 'returns the application by id' do
+ params = { id: application1.id }
+ found = described_class.new(params).execute
+
+ expect(found).to match(application1)
+ end
+ end
+end
diff --git a/spec/finders/fork_projects_finder_spec.rb b/spec/finders/fork_projects_finder_spec.rb
index f0cef7ea406..b3fdffc3331 100644
--- a/spec/finders/fork_projects_finder_spec.rb
+++ b/spec/finders/fork_projects_finder_spec.rb
@@ -1,20 +1,21 @@
require 'spec_helper'
describe ForkProjectsFinder do
- let(:source_project) { create(:project, :empty_repo) }
- let(:private_fork) { create(:project, :private, :empty_repo, name: 'A') }
- let(:internal_fork) { create(:project, :internal, :empty_repo, name: 'B') }
- let(:public_fork) { create(:project, :public, :empty_repo, name: 'C') }
+ include ProjectForksHelper
+
+ let(:source_project) { create(:project, :public, :empty_repo) }
+ let(:private_fork) { fork_project(source_project, nil, name: 'A') }
+ let(:internal_fork) { fork_project(source_project, nil, name: 'B') }
+ let(:public_fork) { fork_project(source_project, nil, name: 'C') }
let(:non_member) { create(:user) }
let(:private_fork_member) { create(:user) }
before do
+ private_fork.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
private_fork.add_developer(private_fork_member)
- source_project.forks << private_fork
- source_project.forks << internal_fork
- source_project.forks << public_fork
+ internal_fork.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
describe '#execute' do
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index b776e9d856a..de9974c45e1 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -9,6 +9,27 @@ describe NotesFinder do
end
describe '#execute' do
+ context 'when notes filter is present' do
+ let!(:comment) { create(:note_on_issue, project: project) }
+ let!(:system_note) { create(:note_on_issue, project: project, system: true) }
+
+ it 'filters system notes' do
+ finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_comments])
+
+ notes = finder.execute
+
+ expect(notes).to match_array(comment)
+ end
+
+ it 'gets all notes' do
+ finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:all_activity])
+
+ notes = finder.execute
+
+ expect(notes).to match_array([comment, system_note])
+ end
+ end
+
it 'finds notes on merge requests' do
create(:note_on_merge_request, project: project)
diff --git a/spec/finders/user_finder_spec.rb b/spec/finders/user_finder_spec.rb
index e53aa50dd33..4771b878b8e 100644
--- a/spec/finders/user_finder_spec.rb
+++ b/spec/finders/user_finder_spec.rb
@@ -3,40 +3,176 @@
require 'spec_helper'
describe UserFinder do
- describe '#execute' do
+ set(:user) { create(:user) }
+
+ describe '#find_by_id' do
+ context 'when the user exists' do
+ it 'returns the user' do
+ found = described_class.new(user.id).find_by_id
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user exists (id as string)' do
+ it 'returns the user' do
+ found = described_class.new(user.id.to_s).find_by_id
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user does not exist' do
+ it 'returns nil' do
+ found = described_class.new(1).find_by_id
+
+ expect(found).to be_nil
+ end
+ end
+ end
+
+ describe '#find_by_username' do
context 'when the user exists' do
it 'returns the user' do
- user = create(:user)
- found = described_class.new(id: user.id).execute
+ found = described_class.new(user.username).find_by_username
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user does not exist' do
+ it 'returns nil' do
+ found = described_class.new("non_existent_username").find_by_username
+
+ expect(found).to be_nil
+ end
+ end
+ end
+
+ describe '#find_by_id_or_username' do
+ context 'when the user exists (id)' do
+ it 'returns the user' do
+ found = described_class.new(user.id).find_by_id_or_username
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user exists (id as string)' do
+ it 'returns the user' do
+ found = described_class.new(user.id.to_s).find_by_id_or_username
expect(found).to eq(user)
end
end
+ context 'when the user exists (username)' do
+ it 'returns the user' do
+ found = described_class.new(user.username).find_by_id_or_username
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user does not exist (username)' do
+ it 'returns nil' do
+ found = described_class.new("non_existent_username").find_by_id_or_username
+
+ expect(found).to be_nil
+ end
+ end
+
context 'when the user does not exist' do
it 'returns nil' do
- found = described_class.new(id: 1).execute
+ found = described_class.new(1).find_by_id_or_username
expect(found).to be_nil
end
end
end
- describe '#execute!' do
+ describe '#find_by_id!' do
+ context 'when the user exists' do
+ it 'returns the user' do
+ found = described_class.new(user.id).find_by_id!
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user exists (id as string)' do
+ it 'returns the user' do
+ found = described_class.new(user.id.to_s).find_by_id!
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user does not exist' do
+ it 'raises ActiveRecord::RecordNotFound' do
+ finder = described_class.new(1)
+
+ expect { finder.find_by_id! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+
+ describe '#find_by_username!' do
context 'when the user exists' do
it 'returns the user' do
- user = create(:user)
- found = described_class.new(id: user.id).execute!
+ found = described_class.new(user.username).find_by_username!
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user does not exist' do
+ it 'raises ActiveRecord::RecordNotFound' do
+ finder = described_class.new("non_existent_username")
+
+ expect { finder.find_by_username! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+
+ describe '#find_by_id_or_username!' do
+ context 'when the user exists (id)' do
+ it 'returns the user' do
+ found = described_class.new(user.id).find_by_id_or_username!
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user exists (id as string)' do
+ it 'returns the user' do
+ found = described_class.new(user.id.to_s).find_by_id_or_username!
expect(found).to eq(user)
end
end
+ context 'when the user exists (username)' do
+ it 'returns the user' do
+ found = described_class.new(user.username).find_by_id_or_username!
+
+ expect(found).to eq(user)
+ end
+ end
+
+ context 'when the user does not exist (username)' do
+ it 'raises ActiveRecord::RecordNotFound' do
+ finder = described_class.new("non_existent_username")
+
+ expect { finder.find_by_id_or_username! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
context 'when the user does not exist' do
it 'raises ActiveRecord::RecordNotFound' do
- finder = described_class.new(id: 1)
+ finder = described_class.new(1)
- expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { finder.find_by_id_or_username! }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
index 4249c52c481..fecf97dc641 100644
--- a/spec/finders/users_finder_spec.rb
+++ b/spec/finders/users_finder_spec.rb
@@ -22,6 +22,12 @@ describe UsersFinder do
expect(users).to contain_exactly(user1)
end
+ it 'filters by username (case insensitive)' do
+ users = described_class.new(user, username: 'joHNdoE').execute
+
+ expect(users).to contain_exactly(user1)
+ end
+
it 'filters by search' do
users = described_class.new(user, search: 'orando').execute
diff --git a/spec/fixtures/security-reports/feature-branch.zip b/spec/fixtures/security-reports/feature-branch.zip
new file mode 100644
index 00000000000..730ce3dc5f8
--- /dev/null
+++ b/spec/fixtures/security-reports/feature-branch.zip
Binary files differ
diff --git a/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
new file mode 100644
index 00000000000..9840382df6f
--- /dev/null
+++ b/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
@@ -0,0 +1,18 @@
+{
+ "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583",
+ "unapproved": [
+ "CVE-2017-15650"
+ ],
+ "vulnerabilities": [
+ {
+ "featurename": "musl",
+ "featureversion": "1.1.14-r15",
+ "vulnerability": "CVE-2017-15650",
+ "namespace": "alpine:v3.4",
+ "description": "",
+ "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650",
+ "severity": "Medium",
+ "fixedby": "1.1.14-r16"
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-dast-report.json b/spec/fixtures/security-reports/feature-branch/gl-dast-report.json
new file mode 100644
index 00000000000..3a308bf047e
--- /dev/null
+++ b/spec/fixtures/security-reports/feature-branch/gl-dast-report.json
@@ -0,0 +1,40 @@
+{
+ "site": {
+ "alerts": [
+ {
+ "sourceid": "3",
+ "wascid": "15",
+ "cweid": "16",
+ "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
+ "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
+ "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
+ "count": "2",
+ "pluginid": "10021",
+ "alert": "X-Content-Type-Options Header Missing",
+ "name": "X-Content-Type-Options Header Missing",
+ "riskcode": "1",
+ "confidence": "2",
+ "riskdesc": "Low (Medium)",
+ "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
+ "instances": [
+ {
+ "param": "X-Content-Type-Options",
+ "method": "GET",
+ "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
+ },
+ {
+ "param": "X-Content-Type-Options",
+ "method": "GET",
+ "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
+ }
+ ]
+ }
+ ],
+ "@ssl": "false",
+ "@port": "80",
+ "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
+ "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
+ },
+ "@generated": "Fri, 13 Apr 2018 09:22:01",
+ "@version": "2.7.0"
+}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
new file mode 100644
index 00000000000..4b47e259c0f
--- /dev/null
+++ b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
@@ -0,0 +1,46 @@
+[
+ {
+ "priority": "Unknown",
+ "file": "pom.xml",
+ "cve": "CVE-2012-4387",
+ "url": "http://struts.apache.org/docs/s2-011.html",
+ "message": "Long parameter name DoS for org.apache.struts/struts2-core",
+ "tools": [
+ "gemnasium"
+ ],
+ "tool": "gemnasium"
+ },
+ {
+ "priority": "Unknown",
+ "file": "pom.xml",
+ "cve": "CVE-2013-1966",
+ "url": "http://struts.apache.org/docs/s2-014.html",
+ "message": "Remote command execution due to flaw in the includeParams attribute of URL and Anchor tags for org.apache.struts/struts2-core",
+ "tools": [
+ "gemnasium"
+ ],
+ "tool": "gemnasium"
+ },
+ {
+ "priority": "Unknown",
+ "file": "pom.xml",
+ "cve": "CVE-2013-2115",
+ "url": "http://struts.apache.org/docs/s2-014.html",
+ "message": "Remote command execution due to flaw in the includeParams attribute of URL and Anchor tags for org.apache.struts/struts2-core",
+ "tools": [
+ "gemnasium"
+ ],
+ "tool": "gemnasium"
+ },
+ {
+ "priority": "Unknown",
+ "file": "pom.xml",
+ "cve": "CVE-2013-2134",
+ "url": "http://struts.apache.org/docs/s2-015.html",
+ "message": "Arbitrary OGNL code execution via unsanitized wildcard matching for org.apache.struts/struts2-core",
+ "tools": [
+ "gemnasium"
+ ],
+ "tool": "gemnasium"
+ }
+]
diff --git a/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json b/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
new file mode 100644
index 00000000000..c1d20fa02fa
--- /dev/null
+++ b/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
@@ -0,0 +1,242 @@
+{
+ "licenses": [
+ {
+ "count": 13,
+ "name": "MIT"
+ },
+ {
+ "count": 2,
+ "name": "New BSD"
+ },
+ {
+ "count": 1,
+ "name": "LGPL"
+ }
+ ],
+ "dependencies": [
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "bundler",
+ "url": "http://bundler.io",
+ "description": "The best way to manage your application's dependencies",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "concurrent-ruby",
+ "url": "http://www.concurrent-ruby.com",
+ "description": "Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "connection_pool",
+ "url": "https://github.com/mperham/connection_pool",
+ "description": "Generic connection pool for Ruby",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "mini_portile2",
+ "url": "http://github.com/flavorjones/mini_portile",
+ "description": "Simplistic port-like solution for developers",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "mustermann",
+ "url": "https://github.com/sinatra/mustermann",
+ "description": "Your personal string matching expert.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "nokogiri",
+ "url": "http://nokogiri.org",
+ "description": "Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "New BSD",
+ "url": "http://opensource.org/licenses/BSD-3-Clause"
+ },
+ "dependency": {
+ "name": "pg",
+ "url": "https://bitbucket.org/ged/ruby-pg",
+ "description": "Pg is the Ruby interface to the {PostgreSQL RDBMS}[http://www.postgresql.org/]",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "New BSD",
+ "url": "http://opensource.org/licenses/BSD-3-Clause"
+ },
+ "dependency": {
+ "name": "puma",
+ "url": "http://puma.io",
+ "description": "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rack",
+ "url": "https://rack.github.io/",
+ "description": "a modular Ruby webserver interface",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rack-protection",
+ "url": "http://github.com/sinatra/sinatra/tree/master/rack-protection",
+ "description": "Protect against typical web attacks, works with all Rack apps, including Rails.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "redis",
+ "url": "https://github.com/redis/redis-rb",
+ "description": "A Ruby client library for Redis",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "LGPL",
+ "url": "http://www.gnu.org/licenses/lgpl.txt"
+ },
+ "dependency": {
+ "name": "sidekiq",
+ "url": "http://sidekiq.org",
+ "description": "Simple, efficient background processing for Ruby",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "sinatra",
+ "url": "http://www.sinatrarb.com/",
+ "description": "Classy web-development dressed in a DSL",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "slim",
+ "url": "http://slim-lang.com/",
+ "description": "Slim is a template language.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "temple",
+ "url": "https://github.com/judofyr/temple",
+ "description": "Template compilation framework in Ruby",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "tilt",
+ "url": "http://github.com/rtomayko/tilt/",
+ "description": "Generic interface to multiple Ruby template engines",
+ "pathes": [
+ "."
+ ]
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-sast-report.json b/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
new file mode 100644
index 00000000000..a85b9be8b5f
--- /dev/null
+++ b/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
@@ -0,0 +1,944 @@
+[
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 1,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 47,
+ "end_line": 47,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken2"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 47,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 41,
+ "end_line": 41,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken1"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 41,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 11,
+ "end_line": 11
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 11,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 12,
+ "end_line": 12
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 12,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 13,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 14,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Pickle library appears to be in use, possible security issue.",
+ "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 15,
+ "end_line": 15
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B301",
+ "value": "B301"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 15,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "ECB mode is insecure",
+ "message": "ECB mode is insecure",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-ECB_MODE",
+ "value": "ECB_MODE",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Cipher with no integrity",
+ "message": "Cipher with no integrity",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-CIPHER_INTEGRITY",
+ "value": "CIPHER_INTEGRITY",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 14,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 10,
+ "end_line": 10
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 10,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports.py",
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports.py",
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 22,
+ "end_line": 22
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B106",
+ "value": "B106",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 22,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'root'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 5,
+ "end_line": 5
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 5,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: ''",
+ "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 13,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 23,
+ "end_line": 23
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 23,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 24,
+ "end_line": 24
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 24,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-function.py",
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-function.py",
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 7,
+ "end_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
+ "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B602",
+ "value": "B602",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 1,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 7,
+ "end_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with loads module.",
+ "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "c/subdir/utils.c",
+ "line": 4,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
+ "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
+ "confidence": "Low",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-362",
+ "value": "362",
+ "url": "https://cwe.mitre.org/data/definitions/362.html"
+ }
+ ],
+ "file": "c/subdir/utils.c",
+ "line": 8,
+ "url": "https://cwe.mitre.org/data/definitions/362.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "cplusplus/src/hello.cpp",
+ "line": 6,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
+ "confidence": "Low",
+ "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "cplusplus/src/hello.cpp",
+ "line": 7,
+ "url": "https://cwe.mitre.org/data/definitions/120.html",
+ "tool": "flawfinder"
+ }
+]
diff --git a/spec/fixtures/security-reports/master.zip b/spec/fixtures/security-reports/master.zip
new file mode 100644
index 00000000000..4684aecb738
--- /dev/null
+++ b/spec/fixtures/security-reports/master.zip
Binary files differ
diff --git a/spec/fixtures/security-reports/master/gl-container-scanning-report.json b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
new file mode 100644
index 00000000000..500c19e3abb
--- /dev/null
+++ b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
@@ -0,0 +1,18 @@
+{
+ "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583",
+ "unapproved": [
+ "CVE-2017-15651"
+ ],
+ "vulnerabilities": [
+ {
+ "featurename": "musl",
+ "featureversion": "1.1.14-r15",
+ "vulnerability": "CVE-2017-15651",
+ "namespace": "alpine:v3.4",
+ "description": "",
+ "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15651",
+ "severity": "Medium",
+ "fixedby": "1.1.14-r16"
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/master/gl-dast-report.json b/spec/fixtures/security-reports/master/gl-dast-report.json
new file mode 100644
index 00000000000..3a308bf047e
--- /dev/null
+++ b/spec/fixtures/security-reports/master/gl-dast-report.json
@@ -0,0 +1,40 @@
+{
+ "site": {
+ "alerts": [
+ {
+ "sourceid": "3",
+ "wascid": "15",
+ "cweid": "16",
+ "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
+ "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
+ "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
+ "count": "2",
+ "pluginid": "10021",
+ "alert": "X-Content-Type-Options Header Missing",
+ "name": "X-Content-Type-Options Header Missing",
+ "riskcode": "1",
+ "confidence": "2",
+ "riskdesc": "Low (Medium)",
+ "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
+ "instances": [
+ {
+ "param": "X-Content-Type-Options",
+ "method": "GET",
+ "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
+ },
+ {
+ "param": "X-Content-Type-Options",
+ "method": "GET",
+ "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
+ }
+ ]
+ }
+ ],
+ "@ssl": "false",
+ "@port": "80",
+ "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
+ "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
+ },
+ "@generated": "Fri, 13 Apr 2018 09:22:01",
+ "@version": "2.7.0"
+}
diff --git a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
new file mode 100644
index 00000000000..b4e4e8e7dd5
--- /dev/null
+++ b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
@@ -0,0 +1,35 @@
+[
+ {
+ "priority": "Unknown",
+ "file": "pom.xml",
+ "cve": "CVE-2012-4386",
+ "url": "http://struts.apache.org/docs/s2-010.html",
+ "message": "CSRF protection bypass for org.apache.struts/struts2-core",
+ "tools": [
+ "gemnasium"
+ ],
+ "tool": "gemnasium"
+ },
+ {
+ "priority": "Unknown",
+ "file": "pom.xml",
+ "cve": "CVE-2012-4387",
+ "url": "http://struts.apache.org/docs/s2-011.html",
+ "message": "Long parameter name DoS for org.apache.struts/struts2-core",
+ "tools": [
+ "gemnasium"
+ ],
+ "tool": "gemnasium"
+ },
+ {
+ "priority": "Unknown",
+ "file": "pom.xml",
+ "cve": "CVE-2013-1966",
+ "url": "http://struts.apache.org/docs/s2-014.html",
+ "message": "Remote command execution due to flaw in the includeParams attribute of URL and Anchor tags for org.apache.struts/struts2-core",
+ "tools": [
+ "gemnasium"
+ ],
+ "tool": "gemnasium"
+ }
+]
diff --git a/spec/fixtures/security-reports/master/gl-license-management-report.json b/spec/fixtures/security-reports/master/gl-license-management-report.json
new file mode 100644
index 00000000000..fe91e4fb7ee
--- /dev/null
+++ b/spec/fixtures/security-reports/master/gl-license-management-report.json
@@ -0,0 +1,150 @@
+{
+ "licenses": [
+ {
+ "count": 10,
+ "name": "MIT"
+ }
+ ],
+ "dependencies": [
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "mini_portile2",
+ "url": "http://github.com/flavorjones/mini_portile",
+ "description": "Simplistic port-like solution for developers",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "mustermann",
+ "url": "https://github.com/sinatra/mustermann",
+ "description": "Your personal string matching expert.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "nokogiri",
+ "url": "http://nokogiri.org",
+ "description": "Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rack",
+ "url": "https://rack.github.io/",
+ "description": "a modular Ruby webserver interface",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rack-protection",
+ "url": "http://github.com/sinatra/sinatra/tree/master/rack-protection",
+ "description": "Protect against typical web attacks, works with all Rack apps, including Rails.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "redis",
+ "url": "https://github.com/redis/redis-rb",
+ "description": "A Ruby client library for Redis",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "sinatra",
+ "url": "http://www.sinatrarb.com/",
+ "description": "Classy web-development dressed in a DSL",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "slim",
+ "url": "http://slim-lang.com/",
+ "description": "Slim is a template language.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "temple",
+ "url": "https://github.com/judofyr/temple",
+ "description": "Template compilation framework in Ruby",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "tilt",
+ "url": "http://github.com/rtomayko/tilt/",
+ "description": "Generic interface to multiple Ruby template engines",
+ "pathes": [
+ "."
+ ]
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/master/gl-sast-report.json b/spec/fixtures/security-reports/master/gl-sast-report.json
new file mode 100644
index 00000000000..a85b9be8b5f
--- /dev/null
+++ b/spec/fixtures/security-reports/master/gl-sast-report.json
@@ -0,0 +1,944 @@
+[
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 1,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 47,
+ "end_line": 47,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken2"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 47,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 41,
+ "end_line": 41,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken1"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 41,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 11,
+ "end_line": 11
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 11,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 12,
+ "end_line": 12
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 12,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 13,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 14,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Pickle library appears to be in use, possible security issue.",
+ "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 15,
+ "end_line": 15
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B301",
+ "value": "B301"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 15,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "ECB mode is insecure",
+ "message": "ECB mode is insecure",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-ECB_MODE",
+ "value": "ECB_MODE",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Cipher with no integrity",
+ "message": "Cipher with no integrity",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-CIPHER_INTEGRITY",
+ "value": "CIPHER_INTEGRITY",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 14,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 10,
+ "end_line": 10
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 10,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports.py",
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports.py",
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 22,
+ "end_line": 22
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B106",
+ "value": "B106",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 22,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'root'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 5,
+ "end_line": 5
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 5,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: ''",
+ "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 13,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 23,
+ "end_line": 23
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 23,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 24,
+ "end_line": 24
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 24,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-function.py",
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-function.py",
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 7,
+ "end_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
+ "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B602",
+ "value": "B602",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 1,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 7,
+ "end_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with loads module.",
+ "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "c/subdir/utils.c",
+ "line": 4,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
+ "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
+ "confidence": "Low",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-362",
+ "value": "362",
+ "url": "https://cwe.mitre.org/data/definitions/362.html"
+ }
+ ],
+ "file": "c/subdir/utils.c",
+ "line": 8,
+ "url": "https://cwe.mitre.org/data/definitions/362.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "cplusplus/src/hello.cpp",
+ "line": 6,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
+ "confidence": "Low",
+ "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "cplusplus/src/hello.cpp",
+ "line": 7,
+ "url": "https://cwe.mitre.org/data/definitions/120.html",
+ "tool": "flawfinder"
+ }
+]
diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb
index cc310766433..8bf378549fe 100644
--- a/spec/helpers/time_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -22,34 +22,17 @@ describe TimeHelper do
describe "#duration_in_numbers" do
using RSpec::Parameterized::TableSyntax
- context "without passing allow_overflow" do
- where(:duration, :formatted_string) do
- 0 | "00:00"
- 1.second | "00:01"
- 42.seconds | "00:42"
- 2.minutes + 1.second | "02:01"
- 3.hours + 2.minutes + 1.second | "03:02:01"
- 30.hours | "06:00:00"
- end
-
- with_them do
- it { expect(duration_in_numbers(duration)).to eq formatted_string }
- end
+ where(:duration, :formatted_string) do
+ 0 | "00:00"
+ 1.second | "00:01"
+ 42.seconds | "00:42"
+ 2.minutes + 1.second | "02:01"
+ 3.hours + 2.minutes + 1.second | "03:02:01"
+ 30.hours | "30:00:00"
end
- context "with allow_overflow = true" do
- where(:duration, :formatted_string) do
- 0 | "00:00:00"
- 1.second | "00:00:01"
- 42.seconds | "00:00:42"
- 2.minutes + 1.second | "00:02:01"
- 3.hours + 2.minutes + 1.second | "03:02:01"
- 30.hours | "30:00:00"
- end
-
- with_them do
- it { expect(duration_in_numbers(duration, true)).to eq formatted_string }
- end
+ with_them do
+ it { expect(duration_in_numbers(duration)).to eq formatted_string }
end
end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index a3be222b7bd..e565ac8c530 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe VisibilityLevelHelper do
+ include ProjectForksHelper
+
let(:project) { build(:project) }
let(:group) { build(:group) }
let(:personal_snippet) { build(:personal_snippet) }
@@ -83,13 +85,13 @@ describe VisibilityLevelHelper do
describe "disallowed_visibility_level?" do
describe "forks" do
- let(:project) { create(:project, :internal) }
- let(:fork_project) { create(:project, forked_from_project: project) }
+ let(:project) { create(:project, :internal) }
+ let(:forked_project) { fork_project(project) }
it "disallows levels" do
- expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
- expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
- expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+ expect(disallowed_visibility_level?(forked_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
+ expect(disallowed_visibility_level?(forked_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+ expect(disallowed_visibility_level?(forked_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
index 9270433dcb7..fd73fb4bfcc 100644
--- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
+++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
@@ -18,8 +18,6 @@ describe('BalsamiqViewer', () => {
});
});
- describe('fileLoaded', () => {});
-
describe('loadFile', () => {
let xhr;
let loadFile;
diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js
index bdee85f90b1..dc5737558c0 100644
--- a/spec/javascripts/collapsed_sidebar_todo_spec.js
+++ b/spec/javascripts/collapsed_sidebar_todo_spec.js
@@ -45,8 +45,10 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
expect(document.querySelector('.js-issuable-todo.sidebar-collapsed-icon')).not.toBeNull();
expect(
- document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-plus-square'),
- ).not.toBeNull();
+ document
+ .querySelector('.js-issuable-todo.sidebar-collapsed-icon svg use')
+ .getAttribute('xlink:href'),
+ ).toContain('todo-add');
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
@@ -68,8 +70,10 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
).not.toBeNull();
expect(
- document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-check-square'),
- ).not.toBeNull();
+ document
+ .querySelector('.js-issuable-todo.sidebar-collapsed-icon svg.todo-undone use')
+ .getAttribute('xlink:href'),
+ ).toContain('todo-done');
done();
});
diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js
index 08e25d2004e..fc94d0bab5b 100644
--- a/spec/javascripts/diffs/components/tree_list_spec.js
+++ b/spec/javascripts/diffs/components/tree_list_spec.js
@@ -53,7 +53,7 @@ describe('Diffs tree list component', () => {
fileHash: 'test',
key: 'index.js',
name: 'index.js',
- path: 'index.js',
+ path: 'app/index.js',
removedLines: 0,
tempFile: true,
type: 'blob',
@@ -104,7 +104,55 @@ describe('Diffs tree list component', () => {
vm.$el.querySelector('.file-row').click();
- expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'index.js');
+ expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'app/index.js');
+ });
+
+ it('renders as file list when renderTreeList is false', done => {
+ vm.renderTreeList = false;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.file-row').length).toBe(1);
+
+ done();
+ });
+ });
+
+ it('renders file paths when renderTreeList is false', done => {
+ vm.renderTreeList = false;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.file-row').textContent).toContain('app/index.js');
+
+ done();
+ });
+ });
+
+ it('hides render buttons when input is focused', done => {
+ const focusEvent = new Event('focus');
+
+ vm.$el.querySelector('.form-control').dispatchEvent(focusEvent);
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.tree-list-view-toggle').style.display).toBe('none');
+
+ done();
+ });
+ });
+
+ it('shows render buttons when input is blurred', done => {
+ const blurEvent = new Event('blur');
+ vm.focusSearch = true;
+
+ vm.$nextTick()
+ .then(() => {
+ vm.$el.querySelector('.form-control').dispatchEvent(blurEvent);
+ })
+ .then(vm.$nextTick)
+ .then(() => {
+ expect(vm.$el.querySelector('.tree-list-view-toggle').style.display).not.toBe('none');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
@@ -117,4 +165,24 @@ describe('Diffs tree list component', () => {
expect(vm.search).toBe('');
});
});
+
+ describe('toggleRenderTreeList', () => {
+ it('updates renderTreeList', () => {
+ expect(vm.renderTreeList).toBe(true);
+
+ vm.toggleRenderTreeList(false);
+
+ expect(vm.renderTreeList).toBe(false);
+ });
+ });
+
+ describe('toggleFocusSearch', () => {
+ it('updates focusSearch', () => {
+ expect(vm.focusSearch).toBe(false);
+
+ vm.toggleFocusSearch(true);
+
+ expect(vm.focusSearch).toBe(true);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index ef367fc09fa..f49dee3696d 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -445,6 +445,14 @@ describe('DiffsStoreUtils', () => {
fileHash: 'test',
},
{
+ newPath: 'app/test/filepathneedstruncating.js',
+ deletedFile: false,
+ newFile: true,
+ removedLines: 0,
+ addedLines: 0,
+ fileHash: 'test',
+ },
+ {
newPath: 'package.json',
deletedFile: true,
newFile: false,
@@ -498,6 +506,19 @@ describe('DiffsStoreUtils', () => {
type: 'blob',
tree: [],
},
+ {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'test',
+ key: 'app/test/filepathneedstruncating.js',
+ name: 'filepathneedstruncating.js',
+ path: 'app/test/filepathneedstruncating.js',
+ removedLines: 0,
+ tempFile: true,
+ type: 'blob',
+ tree: [],
+ },
],
},
],
@@ -527,6 +548,7 @@ describe('DiffsStoreUtils', () => {
'app/index.js',
'app/test',
'app/test/index.js',
+ 'app/test/filepathneedstruncating.js',
'package.json',
]);
});
diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js
index d7338ee0f66..aecab331ead 100644
--- a/spec/javascripts/flash_spec.js
+++ b/spec/javascripts/flash_spec.js
@@ -172,7 +172,7 @@ describe('Flash', () => {
flash('test');
expect(document.querySelector('.flash-text').className).toBe(
- 'flash-text container-fluid container-limited',
+ 'flash-text container-fluid container-limited limit-container-width',
);
});
@@ -180,7 +180,7 @@ describe('Flash', () => {
document.querySelector('.content-wrapper').className = 'js-content-wrapper';
flash('test');
- expect(document.querySelector('.flash-text').className.trim()).toBe('flash-text');
+ expect(document.querySelector('.flash-text').className.trim()).toContain('flash-text');
});
it('removes element after clicking', () => {
diff --git a/spec/javascripts/ide/components/new_dropdown/upload_spec.js b/spec/javascripts/ide/components/new_dropdown/upload_spec.js
index 70b885ede26..878e17ac805 100644
--- a/spec/javascripts/ide/components/new_dropdown/upload_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/upload_spec.js
@@ -40,21 +40,10 @@ describe('new dropdown upload', () => {
describe('readFile', () => {
beforeEach(() => {
- spyOn(FileReader.prototype, 'readAsText');
spyOn(FileReader.prototype, 'readAsDataURL');
});
- it('calls readAsText for text files', () => {
- const file = {
- type: 'text/html',
- };
-
- vm.readFile(file);
-
- expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(file);
- });
-
- it('calls readAsDataURL for non-text files', () => {
+ it('calls readAsDataURL for all files', () => {
const file = {
type: 'images/png',
};
@@ -66,32 +55,37 @@ describe('new dropdown upload', () => {
});
describe('createFile', () => {
- const target = {
- result: 'content',
+ const textTarget = {
+ result: 'base64,cGxhaW4gdGV4dA==',
};
const binaryTarget = {
- result: 'base64,base64content',
+ result: 'base64,w4I=',
+ };
+ const textFile = {
+ name: 'textFile',
+ type: 'text/plain',
};
- const file = {
- name: 'file',
+ const binaryFile = {
+ name: 'binaryFile',
+ type: 'image/png',
};
- it('creates new file', () => {
- vm.createFile(target, file, true);
+ it('creates file in plain text (without encoding) if the file content is plain text', () => {
+ vm.createFile(textTarget, textFile);
expect(vm.$emit).toHaveBeenCalledWith('create', {
- name: file.name,
+ name: textFile.name,
type: 'blob',
- content: target.result,
+ content: 'plain text',
base64: false,
});
});
it('splits content on base64 if binary', () => {
- vm.createFile(binaryTarget, file, false);
+ vm.createFile(binaryTarget, binaryFile);
expect(vm.$emit).toHaveBeenCalledWith('create', {
- name: file.name,
+ name: binaryFile.name,
type: 'blob',
content: binaryTarget.result.split('base64,')[1],
base64: true,
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index c9a1158a14e..df291ade3f7 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -279,8 +279,6 @@ describe('Multi-file store actions', () => {
});
});
- describe('popHistoryState', () => {});
-
describe('scrollToTab', () => {
it('focuses the current active element', done => {
document.body.innerHTML +=
diff --git a/spec/javascripts/image_diff/image_diff_spec.js b/spec/javascripts/image_diff/image_diff_spec.js
index 9a7f288cd6b..21e7b8e2e9b 100644
--- a/spec/javascripts/image_diff/image_diff_spec.js
+++ b/spec/javascripts/image_diff/image_diff_spec.js
@@ -128,16 +128,6 @@ describe('ImageDiff', () => {
});
});
- describe('image loaded', () => {
- beforeEach(() => {
- spyOn(imageUtility, 'isImageLoaded').and.returnValue(true);
- imageDiff = new ImageDiff(element);
- imageDiff.imageEl = imageEl;
- });
-
- it('should renderBadges', () => {});
- });
-
describe('image not loaded', () => {
beforeEach(() => {
spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
diff --git a/spec/javascripts/jobs/components/job_container_item_spec.js b/spec/javascripts/jobs/components/job_container_item_spec.js
new file mode 100644
index 00000000000..8588eda19c8
--- /dev/null
+++ b/spec/javascripts/jobs/components/job_container_item_spec.js
@@ -0,0 +1,73 @@
+import Vue from 'vue';
+import JobContainerItem from '~/jobs/components/job_container_item.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import job from '../mock_data';
+
+describe('JobContainerItem', () => {
+ const Component = Vue.extend(JobContainerItem);
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ const sharedTests = () => {
+ it('displays a status icon', () => {
+ expect(vm.$el).toHaveSpriteIcon(job.status.icon);
+ });
+
+ it('displays the job name', () => {
+ expect(vm.$el).toContainText(job.name);
+ });
+
+ it('displays a link to the job', () => {
+ const link = vm.$el.querySelector('.js-job-link');
+
+ expect(link.href).toBe(job.status.details_path);
+ });
+ };
+
+ describe('when a job is not active and not retied', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ job,
+ isActive: false,
+ });
+ });
+
+ sharedTests();
+ });
+
+ describe('when a job is active', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ job,
+ isActive: true,
+ });
+ });
+
+ sharedTests();
+
+ it('displays an arrow', () => {
+ expect(vm.$el).toHaveSpriteIcon('arrow-right');
+ });
+ });
+
+ describe('when a job is retried', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ job: {
+ ...job,
+ retried: true,
+ },
+ isActive: false,
+ });
+ });
+
+ sharedTests();
+
+ it('displays an icon', () => {
+ expect(vm.$el).toHaveSpriteIcon('retry');
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index ca6fbabeeb6..0398f184c0a 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -1,3 +1,5 @@
+import { TEST_HOST } from 'spec/test_constants';
+
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
@@ -19,7 +21,7 @@ export default {
label: 'passed',
group: 'success',
has_details: true,
- details_path: '/root/ci-mock/-/jobs/4757',
+ details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
favicon:
'/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 7251ce19a90..7714197c821 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -224,6 +224,14 @@ describe('MergeRequestTabs', function() {
expect($('.content-wrapper')).not.toContainElement('.container-limited');
});
+ it('does not add container-limited when fluid layout is prefered', function() {
+ $('.content-wrapper .container-fluid').removeClass('container-limited');
+
+ this.class.expandViewContainer(false);
+
+ expect($('.content-wrapper')).not.toContainElement('.container-limited');
+ });
+
it('does remove container-limited from breadcrumbs', function() {
$('.container-limited').addClass('breadcrumbs');
this.class.expandViewContainer();
diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js
new file mode 100644
index 00000000000..70dd5bb3be5
--- /dev/null
+++ b/spec/javascripts/notes/components/discussion_filter_spec.js
@@ -0,0 +1,60 @@
+import Vue from 'vue';
+import createStore from '~/notes/stores';
+import DiscussionFilter from '~/notes/components/discussion_filter.vue';
+import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
+import { discussionFiltersMock, discussionMock } from '../mock_data';
+
+describe('DiscussionFilter component', () => {
+ let vm;
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+
+ const discussions = [{
+ ...discussionMock,
+ id: discussionMock.id,
+ notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }],
+ }];
+ const Component = Vue.extend(DiscussionFilter);
+ const defaultValue = discussionFiltersMock[0].value;
+
+ store.state.discussions = discussions;
+ vm = mountComponentWithStore(Component, {
+ el: null,
+ store,
+ props: {
+ filters: discussionFiltersMock,
+ defaultValue,
+ },
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders the all filters', () => {
+ expect(vm.$el.querySelectorAll('.dropdown-menu li').length).toEqual(discussionFiltersMock.length);
+ });
+
+ it('renders the default selected item', () => {
+ expect(vm.$el.querySelector('#discussion-filter-dropdown').textContent.trim()).toEqual(discussionFiltersMock[0].title);
+ });
+
+ it('updates to the selected item', () => {
+ const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
+ filterItem.click();
+
+ expect(vm.currentFilter.title).toEqual(filterItem.textContent.trim());
+ });
+
+ it('only updates when selected filter changes', () => {
+ const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button');
+
+ spyOn(vm, 'filterDiscussion');
+ filterItem.click();
+
+ expect(vm.filterDiscussion).not.toHaveBeenCalled();
+ });
+});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index 3e289a6b8e6..06b30375306 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -97,8 +97,7 @@ describe('note_app', () => {
});
it('should render list of notes', done => {
- const note =
- mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[
+ const note = mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[
'/gitlab-org/gitlab-ce/issues/26/discussions.json'
][0].notes[0];
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 9a0e7f34a9c..ad0e793b915 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1244,3 +1244,18 @@ export const discussion3 = {
export const unresolvableDiscussion = {
resolvable: false,
};
+
+export const discussionFiltersMock = [
+ {
+ title: 'Show all activity',
+ value: 0,
+ },
+ {
+ title: 'Show comments only',
+ value: 1,
+ },
+ {
+ title: 'Show system notes only',
+ value: 2,
+ },
+];
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
index 20b5532a837..ce850bc621e 100644
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -14,6 +14,20 @@ const deploymentMockData = {
external_url_formatted: 'diplo.',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
};
const createComponent = () => {
const Component = Vue.extend(deploymentComponent);
@@ -176,4 +190,42 @@ describe('Deployment component', () => {
expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull();
});
});
+
+ describe('with `features.ciEnvironmentsStatusChanges` enabled', () => {
+ beforeEach(() => {
+ window.gon = window.gon || {};
+ window.gon.features = window.gon.features || {};
+ window.gon.features.ciEnvironmentsStatusChanges = true;
+
+ vm = createComponent(deploymentMockData);
+ });
+
+ afterEach(() => {
+ window.gon.features = {};
+ });
+
+ it('renders dropdown with changes', () => {
+ expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).toBeNull();
+ });
+ });
+
+ describe('with `features.ciEnvironmentsStatusChanges` disabled', () => {
+ beforeEach(() => {
+ window.gon = window.gon || {};
+ window.gon.features = window.gon.features || {};
+ window.gon.features.ciEnvironmentsStatusChanges = false;
+
+ vm = createComponent(deploymentMockData);
+ });
+
+ afterEach(() => {
+ delete window.gon.features.ciEnvironmentsStatusChanges;
+ });
+
+ it('renders the old link to the review app', () => {
+ expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 6b5e32fdfd5..d1a064b9f4d 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -7,11 +7,12 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from '../lib/utils/mock_data';
-const returnPromise = data => new Promise((resolve) => {
- resolve({
- data,
+const returnPromise = data =>
+ new Promise(resolve => {
+ resolve({
+ data,
+ });
});
-});
describe('mrWidgetOptions', () => {
let vm;
@@ -135,7 +136,7 @@ describe('mrWidgetOptions', () => {
describe('methods', () => {
describe('checkStatus', () => {
- it('should tell service to check status', (done) => {
+ it('should tell service to check status', done => {
spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData));
spyOn(vm.mr, 'setData');
spyOn(vm, 'handleNotification');
@@ -185,7 +186,7 @@ describe('mrWidgetOptions', () => {
});
describe('fetchDeployments', () => {
- it('should fetch deployments', (done) => {
+ it('should fetch deployments', done => {
spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
vm.fetchDeployments();
@@ -200,7 +201,7 @@ describe('mrWidgetOptions', () => {
});
describe('fetchActionsContent', () => {
- it('should fetch content of Cherry Pick and Revert modals', (done) => {
+ it('should fetch content of Cherry Pick and Revert modals', done => {
spyOn(vm.service, 'fetchMergeActionsContent').and.returnValue(returnPromise('hello world'));
vm.fetchActionsContent();
@@ -251,7 +252,7 @@ describe('mrWidgetOptions', () => {
};
const allArgs = eventHub.$on.calls.allArgs();
- allArgs.forEach((params) => {
+ allArgs.forEach(params => {
const eventName = params[0];
const callback = params[1];
@@ -270,18 +271,6 @@ describe('mrWidgetOptions', () => {
});
});
- describe('handleMounted', () => {
- it('should call required methods to do the initial kick-off', () => {
- spyOn(vm, 'initDeploymentsPolling');
- spyOn(vm, 'setFaviconHelper');
-
- vm.handleMounted();
-
- expect(vm.setFaviconHelper).toHaveBeenCalled();
- expect(vm.initDeploymentsPolling).toHaveBeenCalled();
- });
- });
-
describe('setFavicon', () => {
let faviconElement;
@@ -298,13 +287,14 @@ describe('mrWidgetOptions', () => {
document.body.removeChild(document.getElementById('favicon'));
});
- it('should call setFavicon method', (done) => {
+ it('should call setFavicon method', done => {
vm.mr.ciStatusFaviconPath = overlayDataUrl;
- vm.setFaviconHelper().then(() => {
- expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
- done();
- })
- .catch(done.fail);
+ vm.setFaviconHelper()
+ .then(() => {
+ expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
+ done();
+ })
+ .catch(done.fail);
});
it('should not call setFavicon when there is no ciStatusFaviconPath', () => {
@@ -379,7 +369,7 @@ describe('mrWidgetOptions', () => {
});
describe('rendering relatedLinks', () => {
- beforeEach((done) => {
+ beforeEach(done => {
vm.mr.relatedLinks = {
assignToMe: null,
closing: `
@@ -396,7 +386,7 @@ describe('mrWidgetOptions', () => {
expect(vm.$el.querySelector('.close-related-link')).toBeDefined();
});
- it('does not render if state is nothingToMerge', (done) => {
+ it('does not render if state is nothingToMerge', done => {
vm.mr.state = stateKey.nothingToMerge;
Vue.nextTick(() => {
expect(vm.$el.querySelector('.close-related-link')).toBeNull();
@@ -406,7 +396,7 @@ describe('mrWidgetOptions', () => {
});
describe('rendering source branch removal status', () => {
- it('renders when user cannot remove branch and branch should be removed', (done) => {
+ it('renders when user cannot remove branch and branch should be removed', done => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'readyToMerge';
@@ -423,7 +413,7 @@ describe('mrWidgetOptions', () => {
});
});
- it('does not render in merged state', (done) => {
+ it('does not render in merged state', done => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'merged';
@@ -438,6 +428,20 @@ describe('mrWidgetOptions', () => {
});
describe('rendering deployments', () => {
+ const changes = [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ];
const deploymentMockData = {
id: 15,
name: 'review/diplo',
@@ -449,15 +453,23 @@ describe('mrWidgetOptions', () => {
external_url_formatted: 'diplo.',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes,
};
- beforeEach((done) => {
- vm.mr.deployments.push({
- ...deploymentMockData,
- }, {
- ...deploymentMockData,
- id: deploymentMockData.id + 1,
- });
+ beforeEach(done => {
+ window.gon = window.gon || {};
+ window.gon.features = window.gon.features || {};
+ window.gon.features.ciEnvironmentsStatusChanges = true;
+
+ vm.mr.deployments.push(
+ {
+ ...deploymentMockData,
+ },
+ {
+ ...deploymentMockData,
+ id: deploymentMockData.id + 1,
+ },
+ );
vm.$nextTick(done);
});
@@ -465,5 +477,13 @@ describe('mrWidgetOptions', () => {
it('renders multiple deployments', () => {
expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
});
+
+ it('renders dropdpown with multiple file changes', () => {
+ expect(
+ vm.$el
+ .querySelector('.js-mr-wigdet-deployment-dropdown')
+ .querySelectorAll('.js-filtered-dropdown-result').length,
+ ).toEqual(changes.length);
+ });
});
});
diff --git a/spec/javascripts/vue_shared/components/bar_chart_spec.js b/spec/javascripts/vue_shared/components/bar_chart_spec.js
index 7e91cd6f63f..8f753876e44 100644
--- a/spec/javascripts/vue_shared/components/bar_chart_spec.js
+++ b/spec/javascripts/vue_shared/components/bar_chart_spec.js
@@ -71,12 +71,6 @@ describe('Bar chart component', () => {
expect(barChart.xAxisLocation).toEqual('translate(100, 250)');
});
- it('Contains a total of 4 ticks across the y axis', () => {
- const ticks = barChart.$el.querySelector('.y-axis').querySelectorAll('.tick').length;
-
- expect(ticks).toEqual(4);
- });
-
it('rotates the x axis labels a total of 90 degress (CCW)', () => {
const xAxisLabel = barChart.$el.querySelector('.x-axis').querySelectorAll('text')[0];
diff --git a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
index 21e7afaf78f..4b0b7ba66e5 100644
--- a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
+++ b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
@@ -86,7 +86,7 @@ describe('CI Badge Link Component', () => {
expect(vm.$el.getAttribute('href')).toEqual(statuses[status].details_path);
expect(vm.$el.textContent.trim()).toEqual(statuses[status].text);
- expect(vm.$el.getAttribute('class')).toEqual(`ci-status ci-${statuses[status].group}`);
+ expect(vm.$el.getAttribute('class')).toContain(`ci-status ci-${statuses[status].group}`);
expect(vm.$el.querySelector('svg')).toBeDefined();
return vm;
});
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index 9914c0b70f3..67752c1c455 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -71,4 +71,40 @@ describe('RepoFile', () => {
expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
});
+
+ describe('outputText', () => {
+ beforeEach(done => {
+ createComponent({
+ file: {
+ ...file(),
+ path: 'app/assets/index.js',
+ },
+ level: 0,
+ });
+
+ vm.displayTextKey = 'path';
+
+ vm.$nextTick(done);
+ });
+
+ it('returns text if truncateStart is 0', done => {
+ vm.truncateStart = 0;
+
+ vm.$nextTick(() => {
+ expect(vm.outputText).toBe('app/assets/index.js');
+
+ done();
+ });
+ });
+
+ it('returns text truncated at start', done => {
+ vm.truncateStart = 5;
+
+ vm.$nextTick(() => {
+ expect(vm.outputText).toBe('...ssets/index.js');
+
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js
new file mode 100644
index 00000000000..b71cb36ecf6
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js
@@ -0,0 +1,91 @@
+import Vue from 'vue';
+import component from '~/vue_shared/components/filtered_search_dropdown.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('Filtered search dropdown', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('with an empty array of items', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [],
+ filterKey: '',
+ });
+ });
+
+ it('renders empty list', () => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(0);
+ });
+
+ it('renders filter input', () => {
+ expect(vm.$el.querySelector('.js-filtered-dropdown-input')).not.toBeNull();
+ });
+ });
+
+ describe('when visible numbers is less than the items length', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [{ title: 'One' }, { title: 'Two' }, { title: 'Three' }],
+ visibleItems: 2,
+ filterKey: 'title',
+ });
+ });
+
+ it('it renders only the maximum number provided', () => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(2);
+ });
+ });
+
+ describe('when visible number is bigger than the items lenght', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [{ title: 'One' }, { title: 'Two' }, { title: 'Three' }],
+ filterKey: 'title',
+ });
+ });
+
+ it('it renders the full list of items the maximum number provided', () => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(3);
+ });
+ });
+
+ describe('while filtering', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [
+ { title: 'One' },
+ { title: 'Two/three' },
+ { title: 'Three four' },
+ { title: 'Five' },
+ ],
+ filterKey: 'title',
+ });
+ });
+
+ it('updates the results to match the typed value', done => {
+ vm.$el.querySelector('.js-filtered-dropdown-input').value = 'three';
+ vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input'));
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(2);
+ done();
+ });
+ });
+
+ describe('when no value matches the typed one', () => {
+ it('does not render any result', done => {
+ vm.$el.querySelector('.js-filtered-dropdown-input').value = 'six';
+ vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input'));
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(0);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js
index 6c4bc3602c1..59613faa49f 100644
--- a/spec/javascripts/vue_shared/components/markdown/header_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js
@@ -76,7 +76,7 @@ describe('Markdown field header component', () => {
});
it('blurs preview link after click', done => {
- const link = vm.$el.querySelector('li:nth-child(2) a');
+ const link = vm.$el.querySelector('li:nth-child(2) button');
spyOn(HTMLElement.prototype, 'blur');
link.click();
diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb
index 09bf21b5946..292ab870dad 100644
--- a/spec/lib/gitaly/server_spec.rb
+++ b/spec/lib/gitaly/server_spec.rb
@@ -26,9 +26,7 @@ describe Gitaly::Server do
end
end
- context 'when the storage is not readable' do
- let(:server) { described_class.new('broken') }
-
+ context 'when the storage is not readable', :broken_storage do
it 'returns false' do
expect(server).not_to be_readable
end
@@ -42,9 +40,7 @@ describe Gitaly::Server do
end
end
- context 'when the storage is not writeable' do
- let(:server) { described_class.new('broken') }
-
+ context 'when the storage is not writeable', :broken_storage do
it 'returns false' do
expect(server).not_to be_writeable
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/gzip_file_adapter_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
index 384329dda18..987c6b37aaa 100644
--- a/spec/lib/gitlab/ci/build/artifacts/gzip_file_adapter_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::GzipFileAdapter do
+describe Gitlab::Ci::Build::Artifacts::Adapters::GzipStream do
describe '#initialize' do
context 'when stream is passed' do
let(:stream) { File.open(expand_fixture_path('junit/junit.xml.gz'), 'rb') }
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
new file mode 100644
index 00000000000..ec2dd724b45
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Artifacts::Adapters::RawStream do
+ describe '#initialize' do
+ context 'when stream is passed' do
+ let(:stream) { File.open(expand_fixture_path('junit/junit.xml'), 'rb') }
+
+ it 'initialized' do
+ expect { described_class.new(stream) }.not_to raise_error
+ end
+ end
+
+ context 'when stream is not passed' do
+ let(:stream) { nil }
+
+ it 'raises an error' do
+ expect { described_class.new(stream) }.to raise_error(described_class::InvalidStreamError)
+ end
+ end
+ end
+
+ describe '#each_blob' do
+ let(:adapter) { described_class.new(stream) }
+
+ context 'when file is not empty' do
+ let(:stream) { File.open(expand_fixture_path('junit/junit.xml'), 'rb') }
+
+ it 'iterates content' do
+ expect { |b| adapter.each_blob(&b) }
+ .to yield_with_args(fixture_file('junit/junit.xml'), 'raw')
+ end
+ end
+
+ context 'when file is empty' do
+ let(:stream) { Tempfile.new }
+
+ after do
+ stream.unlink
+ end
+
+ it 'does not iterate content' do
+ expect { |b| adapter.each_blob(&b) }
+ .not_to yield_control
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index e327399d82d..a9a4af1f455 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -112,4 +112,34 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
end
end
end
+
+ context 'generated metadata' do
+ let(:tmpfile) { Tempfile.new('test-metadata') }
+ let(:generator) { CiArtifactMetadataGenerator.new(tmpfile) }
+ let(:entry_count) { 5 }
+
+ before do
+ tmpfile.binmode
+
+ (1..entry_count).each do |index|
+ generator.add_entry("public/test-#{index}.txt")
+ end
+
+ generator.write
+ end
+
+ after do
+ File.unlink(tmpfile.path)
+ end
+
+ describe '#find_entries!' do
+ it 'reads expected number of entries' do
+ stream = File.open(tmpfile.path)
+
+ metadata = described_class.new(stream, 'public', { recursive: true })
+
+ expect(metadata.find_entries!.count).to eq entry_count
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 7cf541447ce..8095a231cf3 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -38,6 +38,8 @@ describe Gitlab::Ci::Config::Entry::Reports do
:dependency_scanning | 'gl-dependency-scanning-report.json'
:container_scanning | 'gl-container-scanning-report.json'
:dast | 'gl-dast-report.json'
+ :license_management | 'gl-license-management-report.json'
+ :performance | 'performance.json'
end
with_them do
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
new file mode 100644
index 00000000000..2e92d5204d6
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Ci::Config::External::File::Base do
+ subject { described_class.new(location) }
+
+ before do
+ allow_any_instance_of(described_class)
+ .to receive(:content).and_return('key: value')
+ end
+
+ describe '#valid?' do
+ context 'when location is not a YAML file' do
+ let(:location) { 'some/file.txt' }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when location has not a valid naming scheme' do
+ let(:location) { 'some/file/.yml' }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when location is a valid .yml extension' do
+ let(:location) { 'some/file/config.yml' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when location is a valid .yaml extension' do
+ let(:location) { 'some/file/config.yaml' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when there are YAML syntax errors' do
+ let(:location) { 'some/file/config.yml' }
+
+ before do
+ allow_any_instance_of(described_class)
+ .to receive(:content).and_return('invalid_syntax')
+ end
+
+ it 'is not a valid file' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to match /does not have valid YAML syntax/
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index 73bb4ccf468..2708d8d5b6b 100644
--- a/spec/lib/gitlab/ci/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::External::File::Local do
+describe Gitlab::Ci::Config::External::File::Local do
let(:project) { create(:project, :repository) }
let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) }
@@ -72,7 +72,7 @@ describe Gitlab::Ci::External::File::Local do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
it 'should return an error message' do
- expect(local_file.error_message).to eq("Local file '#{location}' is not valid.")
+ expect(local_file.error_message).to eq("Local file `#{location}` does not exist!")
end
end
end
diff --git a/spec/lib/gitlab/ci/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index b1819c8960b..7c1a1c38736 100644
--- a/spec/lib/gitlab/ci/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::External::File::Remote do
+describe Gitlab::Ci::Config::External::File::Remote do
let(:remote_file) { described_class.new(location) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:remote_file_content) do
@@ -105,10 +105,53 @@ describe Gitlab::Ci::External::File::Remote do
end
describe "#error_message" do
- let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ subject { remote_file.error_message }
- it 'should return an error message' do
- expect(remote_file.error_message).to eq("Remote file '#{location}' is not valid.")
+ context 'when remote file location is not valid' do
+ let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+
+ it 'returns an error message describing invalid address' do
+ expect(subject).to match /does not have a valid address!/
+ end
+ end
+
+ context 'when timeout error has been raised' do
+ before do
+ WebMock.stub_request(:get, location).to_timeout
+ end
+
+ it 'should returns error message about a timeout' do
+ expect(subject).to match /could not be fetched because of a timeout error!/
+ end
+ end
+
+ context 'when HTTP error has been raised' do
+ before do
+ WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error)
+ end
+
+ it 'should returns error message about a HTTP error' do
+ expect(subject).to match /could not be fetched because of HTTP error!/
+ end
+ end
+
+ context 'when response has 404 status' do
+ before do
+ WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404)
+ end
+
+ it 'should returns error message about a timeout' do
+ expect(subject).to match /could not be fetched because of HTTP code `404` error!/
+ end
+ end
+
+ context 'when the URL is blocked' do
+ let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' }
+
+ it 'should include details about blocked URL' do
+ expect(subject).to eq "Remote file could not be fetched because URL '#{location}' " \
+ 'is blocked: Requests to localhost are not allowed!'
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index d925d6af73d..5b236fe99f1 100644
--- a/spec/lib/gitlab/ci/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::External::Mapper do
+describe Gitlab::Ci::Config::External::Mapper do
let(:project) { create(:project, :repository) }
let(:file_content) do
<<~HEREDOC
@@ -27,7 +27,8 @@ describe Gitlab::Ci::External::Mapper do
end
it 'returns File instances' do
- expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Local)
+ expect(subject.first)
+ .to be_an_instance_of(Gitlab::Ci::Config::External::File::Local)
end
end
@@ -49,7 +50,8 @@ describe Gitlab::Ci::External::Mapper do
end
it 'returns File instances' do
- expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Remote)
+ expect(subject.first)
+ .to be_an_instance_of(Gitlab::Ci::Config::External::File::Remote)
end
end
end
diff --git a/spec/lib/gitlab/ci/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 3c7394f53d2..1a05f716247 100644
--- a/spec/lib/gitlab/ci/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::External::Processor do
+describe Gitlab::Ci::Config::External::Processor do
let(:project) { create(:project, :repository) }
let(:processor) { described_class.new(values, project, '12345') }
@@ -20,8 +20,8 @@ describe Gitlab::Ci::External::Processor do
it 'should raise an error' do
expect { processor.perform }.to raise_error(
- described_class::FileError,
- "Local file '/lib/gitlab/ci/templates/non-existent-file.yml' is not valid."
+ described_class::IncludeError,
+ "Local file `/lib/gitlab/ci/templates/non-existent-file.yml` does not exist!"
)
end
end
@@ -36,8 +36,8 @@ describe Gitlab::Ci::External::Processor do
it 'should raise an error' do
expect { processor.perform }.to raise_error(
- described_class::FileError,
- "Remote file '#{remote_file}' is not valid."
+ described_class::IncludeError,
+ "Remote file `#{remote_file}` could not be fetched because of a socket error!"
)
end
end
@@ -92,7 +92,8 @@ describe Gitlab::Ci::External::Processor do
end
before do
- allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
+ .to receive(:fetch_local_content).and_return(local_file_content)
end
it 'should append the file to the values' do
@@ -131,7 +132,10 @@ describe Gitlab::Ci::External::Processor do
before do
local_file_content = File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml'))
- allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+
+ allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
+ .to receive(:fetch_local_content).and_return(local_file_content)
+
WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
end
@@ -150,11 +154,15 @@ describe Gitlab::Ci::External::Processor do
let(:local_file_content) { 'invalid content file ////' }
before do
- allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
+ .to receive(:fetch_local_content).and_return(local_file_content)
end
it 'should raise an error' do
- expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError)
+ expect { processor.perform }.to raise_error(
+ described_class::IncludeError,
+ "Included file `/lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!"
+ )
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index b43aca8a354..975e11e8cc1 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -1,6 +1,4 @@
-require 'fast_spec_helper'
-
-require_dependency 'active_model'
+require 'spec_helper'
describe Gitlab::Ci::Config do
let(:config) do
@@ -202,8 +200,8 @@ describe Gitlab::Ci::Config do
it 'raises error YamlProcessor validationError' do
expect { config }.to raise_error(
- ::Gitlab::Ci::YamlProcessor::ValidationError,
- "Local file 'invalid' is not valid."
+ described_class::ConfigError,
+ "Included file `invalid` does not have YAML extension!"
)
end
end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index aa53ecd5967..b379b08ad62 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -339,7 +339,7 @@ describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq 'scheduled'
+ expect(status.text).to eq 'delayed'
expect(status.group).to eq 'scheduled'
expect(status.icon).to eq 'status_scheduled'
expect(status.favicon).to eq 'favicon_status_scheduled'
diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
index f98183d6d18..4a52b3ab8de 100644
--- a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
@@ -26,9 +26,9 @@ describe Gitlab::Ci::Status::Build::Scheduled do
context 'when scheduled_at is expired' do
let(:build) { create(:ci_build, :expired_scheduled, project: project) }
- it 'shows 00:00:00' do
+ it 'shows 00:00' do
Timecop.freeze do
- expect(subject.status_tooltip).to include('00:00:00')
+ expect(subject.status_tooltip).to include('00:00')
end
end
end
diff --git a/spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
index 29afa08b56b..f89712d2b03 100644
--- a/spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Scheduled do
+describe Gitlab::Ci::Status::Pipeline::Delayed do
let(:pipeline) { double('pipeline') }
subject do
@@ -9,7 +9,7 @@ describe Gitlab::Ci::Status::Pipeline::Scheduled do
describe '#text' do
it 'overrides status text' do
- expect(subject.text).to eq 'scheduled'
+ expect(subject.text).to eq 'delayed'
end
end
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index d97fdc01109..466087a0e31 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -71,7 +71,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
it 'matches a correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Pipeline::Scheduled]
+ .to eq [Gitlab::Ci::Status::Pipeline::Delayed]
end
it 'extends core status with common pipeline methods' do
diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb
index c35a6f43d5d..b8ca3caa1f7 100644
--- a/spec/lib/gitlab/ci/status/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb
@@ -6,11 +6,11 @@ describe Gitlab::Ci::Status::Scheduled do
end
describe '#text' do
- it { expect(subject.text).to eq 'scheduled' }
+ it { expect(subject.text).to eq 'delayed' }
end
describe '#label' do
- it { expect(subject.label).to eq 'scheduled' }
+ it { expect(subject.label).to eq 'delayed' }
end
describe '#icon' do
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index d0dadfa78da..6c37c157f5d 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -46,4 +46,30 @@ describe Gitlab::HTTP do
end
end
end
+
+ describe 'handle redirect loops' do
+ before do
+ WebMock.stub_request(:any, "http://example.org").to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
+ end
+
+ it 'handles GET requests' do
+ expect { described_class.get('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+
+ it 'handles POST requests' do
+ expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+
+ it 'handles PUT requests' do
+ expect { described_class.put('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+
+ it 'handles DELETE requests' do
+ expect { described_class.delete('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+
+ it 'handles HEAD requests' do
+ expect { described_class.head('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index fe167033941..a63f34b5536 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -229,9 +229,8 @@ project:
- mock_ci_service
- mock_deployment_service
- mock_monitoring_service
-- forked_project_link
+- forked_to_members
- forked_from_project
-- forked_project_links
- forks
- merge_requests
- fork_merge_requests
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 7ebfc61f5e7..b0570680d5a 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -335,7 +335,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
restored_project_json
- expect(project.lfs_enabled).to be_nil
+ expect(project.lfs_enabled).to be_falsey
end
end
diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
new file mode 100644
index 00000000000..da3f5d27b25
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::RoleBinding, '#generate' do
+ let(:role_name) { 'edit' }
+ let(:namespace) { 'my-namespace' }
+ let(:service_account_name) { 'my-service-account' }
+
+ let(:subjects) do
+ [
+ {
+ kind: 'ServiceAccount',
+ name: service_account_name,
+ namespace: namespace
+ }
+ ]
+ end
+
+ let(:role_ref) do
+ {
+ apiGroup: 'rbac.authorization.k8s.io',
+ kind: 'Role',
+ name: role_name
+ }
+ end
+
+ let(:resource) do
+ ::Kubeclient::Resource.new(
+ metadata: { name: "gitlab-#{namespace}", namespace: namespace },
+ roleRef: role_ref,
+ subjects: subjects
+ )
+ end
+
+ subject do
+ described_class.new(
+ role_name: role_name,
+ namespace: namespace,
+ service_account_name: service_account_name
+ ).generate
+ end
+
+ it 'should build a Kubeclient Resource' do
+ is_expected.to eq(resource)
+ end
+end
diff --git a/spec/lib/quality/helm_client_spec.rb b/spec/lib/quality/helm_client_spec.rb
index 553cd8719de..7abb9688d5a 100644
--- a/spec/lib/quality/helm_client_spec.rb
+++ b/spec/lib/quality/helm_client_spec.rb
@@ -1,62 +1,111 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe Quality::HelmClient do
let(:namespace) { 'review-apps-ee' }
let(:release_name) { 'my-release' }
- let(:raw_helm_list_result) do
+ let(:raw_helm_list_page1) do
<<~OUTPUT
- NAME REVISION UPDATED STATUS CHART NAMESPACE
- review-improve-re-2dsd9d 1 Tue Jul 31 15:53:17 2018 FAILED gitlab-0.3.4 #{namespace}
- review-11-1-stabl-3r2fso 1 Mon Jul 30 22:44:14 2018 FAILED gitlab-0.3.3 #{namespace}
- review-49375-css-fk664j 1 Thu Jul 19 11:01:30 2018 FAILED gitlab-0.2.4 #{namespace}
+ {"Next":"review-6709-group-t40qbv",
+ "Releases":[
+ {"Name":"review-qa-60-reor-1mugd1", "Revision":1,"Updated":"Thu Oct 4 17:52:31 2018","Status":"FAILED", "Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-7846-fix-s-261vd6","Revision":1,"Updated":"Thu Oct 4 17:33:29 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-7867-snowp-lzo3iy","Revision":1,"Updated":"Thu Oct 4 17:22:14 2018","Status":"DEPLOYED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-rename-geo-o4a780","Revision":1,"Updated":"Thu Oct 4 17:14:57 2018","Status":"DEPLOYED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-5781-opera-0k93fx","Revision":1,"Updated":"Thu Oct 4 17:06:15 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-6709-group-2pzeec","Revision":1,"Updated":"Thu Oct 4 16:36:59 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-ce-to-ee-2-l554mn","Revision":1,"Updated":"Thu Oct 4 16:27:02 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-epics-e2e-m690eb","Revision":1,"Updated":"Thu Oct 4 16:08:26 2018","Status":"DEPLOYED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-7126-admin-06fae2","Revision":1,"Updated":"Thu Oct 4 15:56:35 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"},
+ {"Name":"review-6983-promo-xyou11","Revision":1,"Updated":"Thu Oct 4 15:15:34 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}
+ ]}
+ OUTPUT
+ end
+ let(:raw_helm_list_page2) do
+ <<~OUTPUT
+ {"Releases":[
+ {"Name":"review-6709-group-t40qbv","Revision":1,"Updated":"Thu Oct 4 17:52:31 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}
+ ]}
OUTPUT
end
subject { described_class.new(namespace: namespace) }
describe '#releases' do
+ it 'raises an error if the Helm command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.releases.to_a }.to raise_error(described_class::CommandFailedError)
+ end
+
it 'calls helm list with default arguments' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm list --namespace "#{namespace}")])
- .and_return(Gitlab::Popen::Result.new([], ''))
+ .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
- subject.releases
+ subject.releases.to_a
end
- it 'calls helm list with given arguments' do
+ it 'calls helm list with extra arguments' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm list --namespace "#{namespace}" --deployed)])
- .and_return(Gitlab::Popen::Result.new([], ''))
+ .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --deployed)])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
- subject.releases(args: ['--deployed'])
+ subject.releases(args: ['--deployed']).to_a
end
it 'returns a list of Release objects' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(helm list --namespace "#{namespace}" --deployed)])
- .and_return(Gitlab::Popen::Result.new([], raw_helm_list_result))
-
- releases = subject.releases(args: ['--deployed'])
-
- expect(releases.size).to eq(3)
- expect(releases[0].name).to eq('review-improve-re-2dsd9d')
- expect(releases[0].revision).to eq(1)
- expect(releases[0].last_update).to eq(Time.parse('Tue Jul 31 15:53:17 2018'))
- expect(releases[0].status).to eq('FAILED')
- expect(releases[0].chart).to eq('gitlab-0.3.4')
- expect(releases[0].namespace).to eq(namespace)
+ .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --deployed)])
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true)))
+
+ releases = subject.releases(args: ['--deployed']).to_a
+
+ expect(releases.size).to eq(1)
+ expect(releases[0]).to have_attributes(
+ name: 'review-6709-group-t40qbv',
+ revision: 1,
+ last_update: Time.parse('Thu Oct 4 17:52:31 2018'),
+ status: 'FAILED',
+ chart: 'gitlab-1.1.3',
+ app_version: 'master',
+ namespace: namespace
+ )
+ end
+
+ it 'automatically paginates releases' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
+ .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)])
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page1, '', double(success?: true)))
+ expect(Gitlab::Popen).to receive(:popen_with_detail).ordered
+ .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --offset review-6709-group-t40qbv)])
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true)))
+
+ releases = subject.releases.to_a
+
+ expect(releases.size).to eq(11)
+ expect(releases.last.name).to eq('review-6709-group-t40qbv')
end
end
describe '#delete' do
+ it 'raises an error if the Helm command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
+ end
+
it 'calls helm delete with default arguments' do
expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["helm delete --purge #{release_name}"])
- .and_return(Gitlab::Popen::Result.new([], '', '', 0))
+ .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name})])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
- expect(subject.delete(release_name: release_name).status).to eq(0)
+ expect(subject.delete(release_name: release_name)).to eq('')
end
end
end
diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb
index 3c0c0d0977a..f35d9464d48 100644
--- a/spec/lib/quality/kubernetes_client_spec.rb
+++ b/spec/lib/quality/kubernetes_client_spec.rb
@@ -1,25 +1,33 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe Quality::KubernetesClient do
- subject { described_class.new(namespace: 'review-apps-ee') }
+ let(:namespace) { 'review-apps-ee' }
+ let(:release_name) { 'my-release' }
+
+ subject { described_class.new(namespace: namespace) }
describe '#cleanup' do
+ it 'raises an error if the Kubernetes command fails' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(kubectl --namespace "#{namespace}" delete ) \
+ 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \
+ "--now -l release=\"#{release_name}\""])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+
+ expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
+ end
+
it 'calls kubectl with the correct arguments' do
- # popen_with_detail will receive an array with a bunch of arguments; we're
- # only concerned with it having the correct namespace and release name
- expect(Gitlab::Popen).to receive(:popen_with_detail) do |args|
- expect(args)
- .to satisfy_one { |arg| arg.start_with?('-n "review-apps-ee" get') }
- expect(args)
- .to satisfy_one { |arg| arg == 'grep "my-release"' }
- expect(args)
- .to satisfy_one { |arg| arg.end_with?('-n "review-apps-ee" delete') }
- end
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(kubectl --namespace "#{namespace}" delete ) \
+ 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \
+ "--now -l release=\"#{release_name}\""])
+ .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
# We're not verifying the output here, just silencing it
- expect { subject.cleanup(release_name: 'my-release') }.to output.to_stdout
+ expect { subject.cleanup(release_name: release_name) }.to output.to_stdout
end
end
end
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
index 034e8a6a4e5..baf16c2ce53 100644
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -31,7 +31,16 @@ describe RenameMoreReservedProjectNames, :delete do
context 'when exception is raised during rename' do
before do
- allow(project).to receive(:rename_repo).and_raise(StandardError)
+ service = instance_double('service')
+
+ allow(service)
+ .to receive(:execute)
+ .and_raise(Projects::AfterRenameService::RenameFailedError)
+
+ allow(Projects::AfterRenameService)
+ .to receive(:new)
+ .with(project)
+ .and_return(service)
end
it 'captures exception from project rename' do
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
index 592ac2b5fb9..7818aa0d560 100644
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -35,7 +35,16 @@ describe RenameReservedProjectNames, :migration, schema: :latest do
context 'when exception is raised during rename' do
before do
- allow(project).to receive(:rename_repo).and_raise(StandardError)
+ service = instance_double('service')
+
+ allow(service)
+ .to receive(:execute)
+ .and_raise(Projects::AfterRenameService::RenameFailedError)
+
+ allow(Projects::AfterRenameService)
+ .to receive(:new)
+ .with(project)
+ .and_return(service)
end
it 'captures exception from project rename' do
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 85fad77a242..fb5bec4108a 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -194,6 +194,14 @@ describe Ci::JobArtifact do
end
end
+ context 'when file format is raw' do
+ let(:artifact) { build(:ci_job_artifact, :codequality, file_format: :raw) }
+
+ it 'iterates blob once' do
+ expect { |b| artifact.each_blob(&b) }.to yield_control.once
+ end
+ end
+
context 'when there are no adapters for the file format' do
let(:artifact) { build(:ci_job_artifact, :junit, file_format: :zip) }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 3b01b39ecab..153244b2159 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -779,6 +779,41 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe 'ref_exists?' do
+ context 'when repository exists' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:project) { create(:project, :repository) }
+
+ where(:tag, :ref, :result) do
+ false | 'master' | true
+ false | 'non-existent-branch' | false
+ true | 'v1.1.0' | true
+ true | 'non-existent-tag' | false
+ end
+
+ with_them do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project, tag: tag, ref: ref)
+ end
+
+ it "correctly detects ref" do
+ expect(pipeline.ref_exists?).to be result
+ end
+ end
+ end
+
+ context 'when repository does not exist' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project, ref: 'master')
+ end
+
+ it 'always returns false' do
+ expect(pipeline.ref_exists?).to eq false
+ end
+ end
+ end
+
context 'with non-empty project' do
let(:project) { create(:project, :repository) }
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 42448b42c8e..d5fb1a9d010 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -17,7 +17,7 @@ describe Clusters::Applications::Runner do
let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') }
it 'updates the application version' do
- expect(application.reload.version).to eq('0.1.31')
+ expect(application.reload.version).to eq('0.1.35')
end
end
end
@@ -45,7 +45,7 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
- expect(subject.version).to eq('0.1.31')
+ expect(subject.version).to eq('0.1.35')
expect(subject).not_to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
@@ -63,7 +63,7 @@ describe Clusters::Applications::Runner do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.1.31')
+ expect(subject.version).to eq('0.1.35')
end
end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 34d321ec604..f5c4b0b66ae 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -11,6 +11,9 @@ describe Clusters::Cluster do
it { is_expected.to have_one(:application_ingress) }
it { is_expected.to have_one(:application_prometheus) }
it { is_expected.to have_one(:application_runner) }
+ it { is_expected.to have_many(:kubernetes_namespaces) }
+ it { is_expected.to have_one(:kubernetes_namespace) }
+
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
it { is_expected.to delegate_method(:status_name).to(:provider) }
@@ -20,6 +23,7 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
+
it { is_expected.to respond_to :project }
describe '.enabled' do
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
new file mode 100644
index 00000000000..dea58fa26c7
--- /dev/null
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Clusters::KubernetesNamespace, type: :model do
+ it { is_expected.to belong_to(:cluster_project) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to have_one(:platform_kubernetes) }
+
+ describe 'namespace uniqueness validation' do
+ let(:cluster_project) { create(:cluster_project) }
+
+ let(:kubernetes_namespace) do
+ build(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project)
+ end
+
+ subject { kubernetes_namespace }
+
+ context 'when cluster is using the namespace' do
+ before do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project,
+ namespace: kubernetes_namespace.namespace)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when cluster is not using the namespace' do
+ it { is_expected.to be_valid }
+ end
+ end
+
+ describe '#set_namespace_and_service_account_to_default' do
+ let(:cluster) { platform.cluster }
+ let(:cluster_project) { create(:cluster_project, cluster: cluster) }
+ let(:kubernetes_namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project)
+ end
+
+ describe 'namespace' do
+ let(:platform) { create(:cluster_platform_kubernetes, namespace: namespace) }
+
+ subject { kubernetes_namespace.namespace }
+
+ context 'when platform has a namespace assigned' do
+ let(:namespace) { 'platform-namespace' }
+
+ it 'should copy the namespace' do
+ is_expected.to eq('platform-namespace')
+ end
+ end
+
+ context 'when platform does not have namespace assigned' do
+ let(:namespace) { nil }
+
+ it 'should set default namespace' do
+ project_slug = "#{cluster_project.project.path}-#{cluster_project.project_id}"
+
+ is_expected.to eq(project_slug)
+ end
+ end
+ end
+
+ describe 'service_account_name' do
+ let(:platform) { create(:cluster_platform_kubernetes) }
+
+ subject { kubernetes_namespace.service_account_name }
+
+ it 'should set a service account name based on namespace' do
+ is_expected.to eq("#{kubernetes_namespace.namespace}-service-account")
+ end
+ end
+ end
+end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 66198d5ee2b..e13eb554add 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -9,6 +9,15 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to be_kind_of(ReactiveCaching) }
it { is_expected.to respond_to :ca_pem }
+ it { is_expected.to validate_exclusion_of(:namespace).in_array(%w(gitlab-managed-apps)) }
+ it { is_expected.to validate_presence_of(:api_url) }
+ it { is_expected.to validate_presence_of(:token) }
+
+ it { is_expected.to delegate_method(:project).to(:cluster) }
+ it { is_expected.to delegate_method(:enabled?).to(:cluster) }
+ it { is_expected.to delegate_method(:managed?).to(:cluster) }
+ it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) }
+
describe 'before_validation' do
context 'when namespace includes upper case' do
let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
@@ -90,6 +99,28 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { expect(kubernetes.save).to be_falsey }
end
end
+
+ describe 'when using reserved namespaces' do
+ subject { build(:cluster_platform_kubernetes, namespace: namespace) }
+
+ context 'when no namespace is manually assigned' do
+ let(:namespace) { nil }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when no reserved namespace is assigned' do
+ let(:namespace) { 'my-namespace' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when reserved namespace is assigned' do
+ let(:namespace) { 'gitlab-managed-apps' }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
end
describe '#kubeclient' do
@@ -117,41 +148,39 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
describe '#actual_namespace' do
- subject { kubernetes.actual_namespace }
-
- let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
+ let(:cluster) { create(:cluster, :project) }
let(:project) { cluster.project }
- let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
- context 'when namespace is present' do
+ let(:platform) do
+ create(:cluster_platform_kubernetes,
+ cluster: cluster,
+ namespace: namespace)
+ end
+
+ subject { platform.actual_namespace }
+
+ context 'with a namespace assigned' do
let(:namespace) { 'namespace-123' }
it { is_expected.to eq(namespace) }
end
- context 'when namespace is not present' do
+ context 'with no namespace assigned' do
let(:namespace) { nil }
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
- end
-
- describe '#default_namespace' do
- subject { kubernetes.send(:default_namespace) }
+ context 'when kubernetes namespace is present' do
+ let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
- let(:kubernetes) { create(:cluster_platform_kubernetes, :configured) }
+ before do
+ kubernetes_namespace
+ end
- context 'when cluster belongs to a project' do
- let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
- let(:project) { cluster.project }
-
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
-
- context 'when cluster belongs to nothing' do
- let!(:cluster) { create(:cluster, platform_kubernetes: kubernetes) }
+ it { is_expected.to eq(kubernetes_namespace.namespace) }
+ end
- it { is_expected.to be_nil }
+ context 'when kubernetes namespace is not present' do
+ it { is_expected.to eq("#{project.path}-#{project.id}") }
+ end
end
end
diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb
index 7d75d6ab345..82ef5a23c18 100644
--- a/spec/models/clusters/project_spec.rb
+++ b/spec/models/clusters/project_spec.rb
@@ -3,4 +3,6 @@ require 'spec_helper'
describe Clusters::Project do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:kubernetes_namespaces) }
+ it { is_expected.to have_one(:kubernetes_namespace) }
end
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
index 729056b6abc..66c1f47d12b 100644
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ b/spec/models/concerns/relative_positioning_spec.rb
@@ -6,9 +6,13 @@ describe RelativePositioning do
let(:issue1) { create(:issue, project: project) }
let(:new_issue) { create(:issue, project: project) }
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
+ describe '.move_to_end' do
+ it 'moves the object to the end' do
+ Issue.move_to_end([issue, issue1])
+
+ expect(issue1.prev_relative_position).to eq issue.relative_position
+ expect(issue.prev_relative_position).to eq nil
+ expect(issue1.next_relative_position).to eq nil
end
end
@@ -59,6 +63,12 @@ describe RelativePositioning do
end
describe '#move_to_end' do
+ before do
+ [issue, issue1].each do |issue|
+ issue.move_to_end && issue.save
+ end
+ end
+
it 'moves issue to the end' do
new_issue.move_to_end
@@ -67,6 +77,12 @@ describe RelativePositioning do
end
describe '#shift_after?' do
+ before do
+ [issue, issue1].each do |issue|
+ issue.move_to_end && issue.save
+ end
+ end
+
it 'returns true' do
issue.update(relative_position: issue1.relative_position - 1)
@@ -81,6 +97,12 @@ describe RelativePositioning do
end
describe '#shift_before?' do
+ before do
+ [issue, issue1].each do |issue|
+ issue.move_to_end && issue.save
+ end
+ end
+
it 'returns true' do
issue.update(relative_position: issue1.relative_position + 1)
@@ -95,6 +117,12 @@ describe RelativePositioning do
end
describe '#move_between' do
+ before do
+ [issue, issue1].each do |issue|
+ issue.move_to_end && issue.save
+ end
+ end
+
it 'positions issue between two other' do
new_issue.move_between(issue, issue1)
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 182070781dd..b8364e0cf88 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -57,7 +57,7 @@ describe Deployment do
last_deployments = described_class.last_for_environment([staging, production, testing])
expect(last_deployments.size).to eq(2)
- expect(last_deployments).to eq(deployments.last(2))
+ expect(last_deployments).to match_array(deployments.last(2))
end
end
end
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
new file mode 100644
index 00000000000..f2eb263c98c
--- /dev/null
+++ b/spec/models/environment_status_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe EnvironmentStatus do
+ let(:deployment) { create(:deployment, :review_app) }
+ let(:environment) { deployment.environment}
+ let(:project) { deployment.project }
+ let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
+
+ subject(:environment_status) { described_class.new(environment, merge_request) }
+
+ it { is_expected.to delegate_method(:id).to(:environment) }
+ it { is_expected.to delegate_method(:name).to(:environment) }
+ it { is_expected.to delegate_method(:project).to(:environment) }
+ it { is_expected.to delegate_method(:deployed_at).to(:deployment).as(:created_at) }
+
+ describe '#project' do
+ subject { environment_status.project }
+
+ it { is_expected.to eq(project) }
+ end
+
+ describe '#merge_request' do
+ subject { environment_status.merge_request }
+
+ it { is_expected.to eq(merge_request) }
+ end
+
+ describe '#deployment' do
+ subject { environment_status.deployment }
+
+ it { is_expected.to eq(deployment) }
+ end
+
+ # $ git diff --stat pages-deploy-target...pages-deploy
+ # .gitlab/route-map.yml | 5 +++++
+ # files/html/500.html | 13 -------------
+ # files/html/page.html | 3 +++
+ # files/js/application.js | 3 +++
+ # files/markdown/ruby-style-guide.md | 4 ++++
+ # pages-deploy.txt | 1 +
+ #
+ # $ cat .gitlab/route-map.yml
+ # - source: /files\/markdown\/(.+)\.md$/
+ # public: '\1.html'
+ #
+ # - source: /files\/(.+)/
+ # public: '\1'
+ describe '#changes' do
+ it 'contains only added and modified public pages' do
+ expect(environment_status.changes).to contain_exactly(
+ {
+ path: 'ruby-style-guide.html',
+ external_url: "#{environment.external_url}/ruby-style-guide.html"
+ }, {
+ path: 'html/page.html',
+ external_url: "#{environment.external_url}/html/page.html"
+ }
+ )
+ end
+ end
+end
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
deleted file mode 100644
index 32e33e8f42f..00000000000
--- a/spec/models/forked_project_link_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'spec_helper'
-
-describe ForkedProjectLink, "add link on fork" do
- include ProjectForksHelper
-
- let(:project_from) { create(:project, :repository) }
- let(:project_to) { fork_project(project_from, user) }
- let(:user) { create(:user) }
-
- before do
- project_from.add_reporter(user)
- end
-
- it 'project_from knows its forks' do
- _ = project_to
-
- expect(project_from.forks.count).to eq(1)
- end
-
- it "project_to knows it is forked" do
- expect(project_to.forked?).to be_truthy
- end
-
- it "project knows who it is forked from" do
- expect(project_to.forked_from_project).to eq(project_from)
- end
-
- context 'project_to is pending_delete' do
- before do
- project_to.update!(pending_delete: true)
- end
-
- it { expect(project_from.forks.count).to eq(0) }
- end
-
- context 'project_from is pending_delete' do
- before do
- project_from.update!(pending_delete: true)
- end
-
- it { expect(project_to.forked_from_project).to be_nil }
- end
-
- describe '#forked?' do
- let(:project_to) { create(:project, :repository, forked_project_link: forked_project_link) }
- let(:forked_project_link) { create(:forked_project_link) }
-
- before do
- forked_project_link.forked_from_project = project_from
- forked_project_link.forked_to_project = project_to
- forked_project_link.save!
- end
-
- it "project_to knows it is forked" do
- expect(project_to.forked?).to be_truthy
- end
-
- it "project_from is not forked" do
- expect(project_from.forked?).to be_falsey
- end
-
- it "project_to.destroy destroys fork_link" do
- project_to.destroy
-
- expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false)
- end
- end
-end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 1783dd3206b..f9be61e4768 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -865,5 +865,29 @@ describe Note do
note.save!
end
end
+
+ describe '#with_notes_filter' do
+ let!(:comment) { create(:note) }
+ let!(:system_note) { create(:note, system: true) }
+
+ context 'when notes filter is nil' do
+ subject { described_class.with_notes_filter(nil) }
+
+ it { is_expected.to include(comment, system_note) }
+ end
+
+ context 'when notes filter is set to all notes' do
+ subject { described_class.with_notes_filter(UserPreference::NOTES_FILTERS[:all_notes]) }
+
+ it { is_expected.to include(comment, system_note) }
+ end
+
+ context 'when notes filter is set to only comments' do
+ subject { described_class.with_notes_filter(UserPreference::NOTES_FILTERS[:only_comments]) }
+
+ it { is_expected.to include(comment) }
+ it { is_expected.not_to include(system_note) }
+ end
+ end
end
end
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index f4f7afb1b92..ee84fa95f0e 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -245,6 +245,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do
end
def bamboo_response(result_key: 42, build_state: 'success', size: 1)
- %Q({"results":{"results":{"size":"#{size}","result":{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}}}})
+ # reference: https://docs.atlassian.com/atlassian-bamboo/REST/6.2.5/#d2e786
+ %Q({"results":{"results":{"size":"#{size}","result":[{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}]}}})
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 88f70a88210..62a38c66d99 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -23,7 +23,6 @@ describe Project do
it { is_expected.to have_many(:deploy_keys) }
it { is_expected.to have_many(:hooks) }
it { is_expected.to have_many(:protected_branches) }
- it { is_expected.to have_one(:forked_project_link) }
it { is_expected.to have_one(:slack_service) }
it { is_expected.to have_one(:microsoft_teams_service) }
it { is_expected.to have_one(:mattermost_service) }
@@ -56,7 +55,7 @@ describe Project do
it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
it { is_expected.to have_one(:last_event).class_name('Event') }
- it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
+ it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
it { is_expected.to have_many(:commit_statuses) }
it { is_expected.to have_many(:pipelines) }
@@ -77,7 +76,8 @@ describe Project do
it { is_expected.to have_many(:lfs_objects_projects) }
it { is_expected.to have_many(:project_group_links) }
it { is_expected.to have_many(:notification_settings).dependent(:delete_all) }
- it { is_expected.to have_many(:forks).through(:forked_project_links) }
+ it { is_expected.to have_many(:forked_to_members).class_name('ForkNetworkMember') }
+ it { is_expected.to have_many(:forks).through(:forked_to_members) }
it { is_expected.to have_many(:uploads) }
it { is_expected.to have_many(:pipeline_schedules) }
it { is_expected.to have_many(:members_and_requesters) }
@@ -1380,7 +1380,7 @@ describe Project do
context 'when checking on forked project' do
let(:project) { create(:project, :internal) }
- let(:forked_project) { create(:project, forked_from_project: project) }
+ let(:forked_project) { fork_project(project) }
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
@@ -1992,9 +1992,12 @@ describe Project do
let(:import_jid) { '123' }
context 'forked' do
- let(:forked_project_link) { create(:forked_project_link, :forked_to_empty_project) }
- let(:forked_from_project) { forked_project_link.forked_from_project }
- let(:project) { forked_project_link.forked_to_project }
+ let(:forked_from_project) { create(:project, :repository) }
+ let(:project) { create(:project) }
+
+ before do
+ fork_project(forked_from_project, nil, target_project: project)
+ end
it 'schedules a RepositoryForkWorker job' do
expect(RepositoryForkWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
@@ -2281,6 +2284,12 @@ describe Project do
end
end
+ describe '#forks' do
+ it 'includes direct forks of the project' do
+ expect(project.forks).to contain_exactly(forked_project)
+ end
+ end
+
describe '#lfs_storage_project' do
it 'returns self for non-forks' do
expect(project.lfs_storage_project).to eq project
@@ -2956,88 +2965,6 @@ describe Project do
end
end
- describe '#rename_repo' do
- before do
- # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
- # call. This makes testing a bit easier.
- allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
- stub_feature_flags(skip_hashed_storage_upgrade: false)
- end
-
- it 'renames a repository' do
- stub_container_registry_config(enabled: false)
-
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}")
- .and_return(true)
-
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
- .and_return(true)
-
- expect_any_instance_of(SystemHooksService)
- .to receive(:execute_hooks_for)
- .with(project, :rename)
-
- expect_any_instance_of(Gitlab::UploadsTransfer)
- .to receive(:rename_project)
- .with('foo', project.path, project.namespace.full_path)
-
- expect(project).to receive(:expire_caches_before_rename)
-
- project.rename_repo
- end
-
- context 'container registry with images' do
- let(:container_repository) { create(:container_repository) }
-
- before do
- stub_container_registry_config(enabled: true)
- stub_container_registry_tags(repository: :any, tags: ['tag'])
- project.container_repositories << container_repository
- end
-
- subject { project.rename_repo }
-
- it { expect { subject }.to raise_error(StandardError) }
- end
-
- context 'gitlab pages' do
- before do
- expect(project_storage).to receive(:rename_repo) { true }
- end
-
- it 'moves pages folder to new location' do
- expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
-
- project.rename_repo
- end
- end
-
- context 'attachments' do
- before do
- expect(project_storage).to receive(:rename_repo) { true }
- end
-
- it 'moves uploads folder to new location' do
- expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project)
-
- project.rename_repo
- end
- end
-
- it 'updates project full path in .git/config' do
- allow(project_storage).to receive(:rename_repo).and_return(true)
-
- project.rename_repo
-
- expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
- end
- end
-
describe '#pages_path' do
it 'returns a path where pages are stored' do
expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
@@ -3128,91 +3055,6 @@ describe Project do
end
end
- describe '#rename_repo' do
- before do
- # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
- # call. This makes testing a bit easier.
- allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
- stub_feature_flags(skip_hashed_storage_upgrade: false)
- end
-
- context 'migration to hashed storage' do
- it 'calls HashedStorageMigrationService with correct options' do
- project = create(:project, :repository, :legacy_storage)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
-
- expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service|
- expect(service).to receive(:execute).and_return(true)
- end
-
- project.rename_repo
- end
- end
-
- it 'renames a repository' do
- stub_container_registry_config(enabled: false)
-
- expect(gitlab_shell).not_to receive(:mv_repository)
-
- expect_any_instance_of(SystemHooksService)
- .to receive(:execute_hooks_for)
- .with(project, :rename)
-
- expect(project).to receive(:expire_caches_before_rename)
-
- project.rename_repo
- end
-
- context 'container registry with images' do
- let(:container_repository) { create(:container_repository) }
-
- before do
- stub_container_registry_config(enabled: true)
- stub_container_registry_tags(repository: :any, tags: ['tag'])
- project.container_repositories << container_repository
- end
-
- subject { project.rename_repo }
-
- it { expect { subject }.to raise_error(StandardError) }
- end
-
- context 'gitlab pages' do
- it 'moves pages folder to new location' do
- expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
-
- project.rename_repo
- end
- end
-
- context 'attachments' do
- it 'keeps uploads folder location unchanged' do
- expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project)
-
- project.rename_repo
- end
-
- context 'when not rolled out' do
- let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }
-
- it 'moves pages folder to hashed storage' do
- expect_next_instance_of(Projects::HashedStorage::MigrateAttachmentsService) do |service|
- expect(service).to receive(:execute)
- end
-
- project.rename_repo
- end
- end
- end
-
- it 'updates project full path in .git/config' do
- project.rename_repo
-
- expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
- end
- end
-
describe '#pages_path' do
it 'returns a path where pages are stored' do
expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb
new file mode 100644
index 00000000000..64d9d9a78b4
--- /dev/null
+++ b/spec/models/user_preference_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe UserPreference do
+ describe '#set_notes_filter' do
+ let(:issuable) { build_stubbed(:issue) }
+ let(:user_preference) { create(:user_preference) }
+ let(:only_comments) { described_class::NOTES_FILTERS[:only_comments] }
+
+ it 'returns updated discussion filter' do
+ filter_name =
+ user_preference.set_notes_filter(only_comments, issuable)
+
+ expect(filter_name).to eq(only_comments)
+ end
+
+ it 'updates discussion filter for issuable class' do
+ user_preference.set_notes_filter(only_comments, issuable)
+
+ expect(user_preference.reload.issue_notes_filter).to eq(only_comments)
+ end
+
+ context 'when notes_filter parameter is invalid' do
+ it 'returns the current notes filter' do
+ user_preference.set_notes_filter(only_comments, issuable)
+
+ expect(user_preference.set_notes_filter(9999, issuable)).to eq(only_comments)
+ end
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 99d17f563d9..b3474e74aa4 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -715,6 +715,15 @@ describe User do
end
end
+ describe 'ensure user preference' do
+ it 'has user preference upon user initialization' do
+ user = build(:user)
+
+ expect(user.user_preference).to be_present
+ expect(user.user_preference).not_to be_persisted
+ end
+ end
+
describe 'ensure incoming email token' do
it 'has incoming email token' do
user = create(:user)
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index a42d1f3d399..170e0ac5717 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -40,21 +40,23 @@ describe Ci::BuildRunnerPresenter do
context "with reports" do
Ci::JobArtifact::DEFAULT_FILE_NAMES.each do |file_type, filename|
- let(:report) { { "#{file_type}": [filename] } }
- let(:build) { create(:ci_build, options: { artifacts: { reports: report } } ) }
-
- let(:report_expectation) do
- {
- name: filename,
- artifact_type: :"#{file_type}",
- artifact_format: :gzip,
- paths: [filename],
- when: 'always'
- }
- end
-
- it 'presents correct hash' do
- expect(presenter.artifacts.first).to include(report_expectation)
+ context file_type.to_s do
+ let(:report) { { "#{file_type}": [filename] } }
+ let(:build) { create(:ci_build, options: { artifacts: { reports: report } } ) }
+
+ let(:report_expectation) do
+ {
+ name: filename,
+ artifact_type: :"#{file_type}",
+ artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(file_type),
+ paths: [filename],
+ when: 'always'
+ }
+ end
+
+ it 'presents correct hash' do
+ expect(presenter.artifacts.first).to include(report_expectation)
+ end
end
end
end
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
index f56bc932f40..270e12bf201 100644
--- a/spec/requests/api/applications_spec.rb
+++ b/spec/requests/api/applications_spec.rb
@@ -5,6 +5,7 @@ describe API::Applications, :api do
let(:admin_user) { create(:user, admin: true) }
let(:user) { create(:user, admin: false) }
+ let!(:application) { create(:application, name: 'another_application', redirect_uri: 'http://other_application.url', scopes: '') }
describe 'POST /applications' do
context 'authenticated and authorized user' do
@@ -15,7 +16,7 @@ describe API::Applications, :api do
application = Doorkeeper::Application.find_by(name: 'application_name', redirect_uri: 'http://application.url')
- expect(response).to have_http_status 201
+ expect(response).to have_gitlab_http_status(201)
expect(json_response).to be_a Hash
expect(json_response['application_id']).to eq application.uid
expect(json_response['secret']).to eq application.secret
@@ -27,7 +28,7 @@ describe API::Applications, :api do
post api('/applications', admin_user), name: 'application_name', redirect_uri: 'wrong_url_format', scopes: ''
end.not_to change { Doorkeeper::Application.count }
- expect(response).to have_http_status 400
+ expect(response).to have_gitlab_http_status(400)
expect(json_response).to be_a Hash
expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.')
end
@@ -37,7 +38,7 @@ describe API::Applications, :api do
post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: ''
end.not_to change { Doorkeeper::Application.count }
- expect(response).to have_http_status 400
+ expect(response).to have_gitlab_http_status(400)
expect(json_response).to be_a Hash
expect(json_response['error']).to eq('name is missing')
end
@@ -47,7 +48,7 @@ describe API::Applications, :api do
post api('/applications', admin_user), name: 'application_name', scopes: ''
end.not_to change { Doorkeeper::Application.count }
- expect(response).to have_http_status 400
+ expect(response).to have_gitlab_http_status(400)
expect(json_response).to be_a Hash
expect(json_response['error']).to eq('redirect_uri is missing')
end
@@ -57,7 +58,7 @@ describe API::Applications, :api do
post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url'
end.not_to change { Doorkeeper::Application.count }
- expect(response).to have_http_status 400
+ expect(response).to have_gitlab_http_status(400)
expect(json_response).to be_a Hash
expect(json_response['error']).to eq('scopes is missing')
end
@@ -69,7 +70,7 @@ describe API::Applications, :api do
post api('/applications', user), name: 'application_name', redirect_uri: 'http://application.url', scopes: ''
end.not_to change { Doorkeeper::Application.count }
- expect(response).to have_http_status 403
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -79,7 +80,62 @@ describe API::Applications, :api do
post api('/applications'), name: 'application_name', redirect_uri: 'http://application.url'
end.not_to change { Doorkeeper::Application.count }
- expect(response).to have_http_status 401
+ expect(response).to have_gitlab_http_status(401)
+ end
+ end
+ end
+
+ describe 'GET /applications' do
+ context 'authenticated and authorized user' do
+ it 'can list application' do
+ get api('/applications', admin_user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_a(Array)
+ end
+ end
+
+ context 'authorized user without authorization' do
+ it 'cannot list application' do
+ get api('/applications', user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'non-authenticated user' do
+ it 'cannot list application' do
+ get api('/applications')
+
+ expect(response).to have_gitlab_http_status(401)
+ end
+ end
+ end
+
+ describe 'DELETE /applications/:id' do
+ context 'authenticated and authorized user' do
+ it 'can delete an application' do
+ expect do
+ delete api("/applications/#{application.id}", admin_user)
+ end.to change { Doorkeeper::Application.count }.by(-1)
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+ end
+
+ context 'authorized user without authorization' do
+ it 'cannot delete an application' do
+ delete api("/applications/#{application.id}", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'non-authenticated user' do
+ it 'cannot delete an application' do
+ delete api("/applications/#{application.id}")
+
+ expect(response).to have_gitlab_http_status(401)
end
end
end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 0a789d58fd8..cca449e9e56 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -368,6 +368,14 @@ describe API::Helpers do
it_behaves_like 'successful sudo'
end
+ context 'when providing username (case insensitive)' do
+ before do
+ env[API::Helpers::SUDO_HEADER] = user.username.upcase
+ end
+
+ it_behaves_like 'successful sudo'
+ end
+
context 'when providing user ID' do
before do
env[API::Helpers::SUDO_HEADER] = user.id.to_s
@@ -386,6 +394,14 @@ describe API::Helpers do
it_behaves_like 'successful sudo'
end
+ context 'when providing username (case insensitive)' do
+ before do
+ set_param(API::Helpers::SUDO_PARAM, user.username.upcase)
+ end
+
+ it_behaves_like 'successful sudo'
+ end
+
context 'when providing user ID' do
before do
set_param(API::Helpers::SUDO_PARAM, user.id.to_s)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index c8e98e6024c..22e5a7c7174 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1196,7 +1196,7 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(201)
expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
- expect(project_fork_target.forked_project_link).to be_present
+ expect(project_fork_target.fork_network_member).to be_present
expect(project_fork_target).to be_forked
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 09c1d016081..e6d01c9689f 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -51,6 +51,15 @@ describe API::Users do
expect(json_response[0]['username']).to eq(user.username)
end
+ it "returns the user when a valid `username` parameter is passed (case insensitive)" do
+ get api("/users"), username: user.username.upcase
+
+ expect(response).to match_response_schema('public_api/v4/user/basics')
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]['id']).to eq(user.id)
+ expect(json_response[0]['username']).to eq(user.username)
+ end
+
it "returns an empty response when an invalid `username` parameter is passed" do
get api("/users"), username: 'invalid'
@@ -132,6 +141,14 @@ describe API::Users do
expect(json_response.first['username']).to eq(omniauth_user.username)
end
+ it "returns one user (case insensitive)" do
+ get api("/users?username=#{omniauth_user.username.upcase}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basics')
+ expect(response).to include_pagination_headers
+ expect(json_response.first['username']).to eq(omniauth_user.username)
+ end
+
it "returns a 403 when non-admin user searches by external UID" do
get api("/users?extern_uid=#{omniauth_user.identities.first.extern_uid}&provider=#{omniauth_user.identities.first.provider}", user)
@@ -343,6 +360,12 @@ describe API::Users do
let(:path) { "/users/#{user.username}/status" }
end
end
+
+ context 'when finding the user by username (case insensitive)' do
+ it_behaves_like 'rendering user status' do
+ let(:path) { "/users/#{user.username.upcase}/status" }
+ end
+ end
end
describe "POST /users" do
@@ -528,6 +551,18 @@ describe API::Users do
expect(json_response['message']).to eq('Username has already been taken')
end
+ it 'returns 409 conflict error if same username exists (case insensitive)' do
+ expect do
+ post api('/users', admin),
+ name: 'foo',
+ email: 'foo@example.com',
+ password: 'password',
+ username: 'TEST'
+ end.to change { User.count }.by(0)
+ expect(response).to have_gitlab_http_status(409)
+ expect(json_response['message']).to eq('Username has already been taken')
+ end
+
it 'creates user with new identity' do
post api("/users", admin), attributes_for(:user, provider: 'github', extern_uid: '67890')
@@ -749,6 +784,14 @@ describe API::Users do
expect(response).to have_gitlab_http_status(409)
expect(@user.reload.username).to eq(@user.username)
end
+
+ it 'returns 409 conflict error if username taken (case insensitive)' do
+ @user_id = User.all.last.id
+ put api("/users/#{@user.id}", admin), username: 'TEST'
+
+ expect(response).to have_gitlab_http_status(409)
+ expect(@user.reload.username).to eq(@user.username)
+ end
end
end
diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb
new file mode 100644
index 00000000000..6894c65d639
--- /dev/null
+++ b/spec/serializers/environment_status_entity_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe EnvironmentStatusEntity do
+ let(:user) { create(:user) }
+ let(:request) { double('request') }
+
+ let(:deployment) { create(:deployment, :review_app) }
+ let(:environment) { deployment.environment}
+ let(:project) { deployment.project }
+ let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
+
+ let(:environment_status) { EnvironmentStatus.new(environment, merge_request) }
+ let(:entity) { described_class.new(environment_status, request: request) }
+
+ subject { entity.as_json }
+
+ before do
+ allow(request).to receive(:current_user).and_return(user)
+ end
+
+ it { is_expected.to include(:id) }
+ it { is_expected.to include(:name) }
+ it { is_expected.to include(:url) }
+ it { is_expected.to include(:external_url) }
+ it { is_expected.to include(:external_url_formatted) }
+ it { is_expected.to include(:deployed_at) }
+ it { is_expected.to include(:deployed_at_formatted) }
+ it { is_expected.to include(:changes) }
+
+ it { is_expected.not_to include(:stop_url) }
+ it { is_expected.not_to include(:metrics_url) }
+ it { is_expected.not_to include(:metrics_monitoring_url) }
+
+ context 'when :ci_environments_status_changes feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_environments_status_changes: false)
+ end
+
+ it { is_expected.not_to include(:changes) }
+ end
+
+ context 'when the user is project maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it { is_expected.to include(:stop_url) }
+ end
+end
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 0ba2539a717..5bf8aa7f23f 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe MergeRequestWidgetEntity do
+ include ProjectForksHelper
+
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) }
@@ -206,12 +208,12 @@ describe MergeRequestWidgetEntity do
describe 'when source project is deleted' do
let(:project) { create(:project, :repository) }
- let(:fork_project) { create(:project, :repository, forked_from_project: project) }
- let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) }
+ let(:forked_project) { fork_project(project) }
+ let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project) }
it 'returns a blank rebase_path' do
allow(merge_request).to receive(:should_be_rebased?).and_return(true)
- fork_project.destroy
+ forked_project.destroy
merge_request.reload
entity = described_class.new(merge_request, request: request).as_json
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index 6337ee7d724..daf5dfba6b1 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -10,6 +10,9 @@ describe ApplicationSettings::UpdateService do
before do
# So the caching behaves like it would in production
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+
+ # Creating these settings first ensures they're used by other factories
+ application_settings
end
describe 'updating terms' do
diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb
new file mode 100644
index 00000000000..32fd98e6ef9
--- /dev/null
+++ b/spec/services/audit_event_service_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuditEventService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:project_member) { create(:project_member, user: user) }
+ let(:service) { described_class.new(user, project, { action: :destroy }) }
+ let(:logger) { instance_double(Gitlab::AuditJsonLogger) }
+
+ describe '#security_event' do
+ before do
+ expect(service).to receive(:file_logger).and_return(logger)
+ end
+
+ it 'creates an event and logs to a file' do
+ expect(logger).to receive(:info).with(author_id: user.id,
+ entity_id: project.id,
+ entity_type: "Project",
+ action: :destroy)
+
+ expect { service.security_event }.to change(SecurityEvent, :count).by(1)
+ end
+ end
+end
diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb
index 9f47439dc4a..9a53b32394d 100644
--- a/spec/services/ci/process_build_service_spec.rb
+++ b/spec/services/ci/process_build_service_spec.rb
@@ -11,213 +11,135 @@ describe Ci::ProcessBuildService, '#execute' do
project.add_maintainer(user)
end
- shared_examples_for 'Enqueuing properly' do |valid_statuses_for_when|
- valid_statuses_for_when.each do |status_for_prior_stages|
- context "when status for prior stages is #{status_for_prior_stages}" do
- let(:current_status) { status_for_prior_stages }
-
- %w[created skipped manual scheduled].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, when: when_option, user: user, project: project) }
-
- it 'enqueues the build' do
- expect { subject }.to change { build.status }.to('pending')
- end
- end
- end
+ context 'when build has on_success option' do
+ let(:build) { create(:ci_build, :created, when: :on_success, user: user, project: project) }
- %w[pending running success failed canceled].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, when: when_option, user: user, project: project) }
+ context 'when current status is success' do
+ let(:current_status) { 'success' }
- it 'does not change the build status' do
- expect { subject }.not_to change { build.status }
- end
- end
- end
+ it 'changes the build status' do
+ expect { subject }.to change { build.status }.to('pending')
end
end
- (HasStatus::AVAILABLE_STATUSES - valid_statuses_for_when).each do |status_for_prior_stages|
- let(:current_status) { status_for_prior_stages }
-
- context "when status for prior stages is #{status_for_prior_stages}" do
- %w[created pending].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, when: when_option, user: user, project: project) }
+ context 'when current status is failed' do
+ let(:current_status) { 'failed' }
- it 'skips the build' do
- expect { subject }.to change { build.status }.to('skipped')
- end
- end
- end
-
- (HasStatus::AVAILABLE_STATUSES - %w[created pending]).each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, when: when_option, user: user, project: project) }
-
- it 'does not change build status' do
- expect { subject }.not_to change { build.status }
- end
- end
- end
+ it 'does not change the build status' do
+ expect { subject }.to change { build.status }.to('skipped')
end
end
end
- shared_examples_for 'Actionizing properly' do |valid_statuses_for_when|
- valid_statuses_for_when.each do |status_for_prior_stages|
- context "when status for prior stages is #{status_for_prior_stages}" do
- let(:current_status) { status_for_prior_stages }
-
- %w[created].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, :actionable, user: user, project: project) }
-
- it 'enqueues the build' do
- expect { subject }.to change { build.status }.to('manual')
- end
- end
- end
+ context 'when build has on_failure option' do
+ let(:build) { create(:ci_build, :created, when: :on_failure, user: user, project: project) }
- %w[manual skipped pending running success failed canceled scheduled].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, :actionable, user: user, project: project) }
+ context 'when current status is success' do
+ let(:current_status) { 'success' }
- it 'does not change the build status' do
- expect { subject }.not_to change { build.status }
- end
- end
- end
+ it 'changes the build status' do
+ expect { subject }.to change { build.status }.to('skipped')
end
end
- (HasStatus::AVAILABLE_STATUSES - valid_statuses_for_when).each do |status_for_prior_stages|
- let(:current_status) { status_for_prior_stages }
-
- context "when status for prior stages is #{status_for_prior_stages}" do
- %w[created pending].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, :actionable, user: user, project: project) }
-
- it 'skips the build' do
- expect { subject }.to change { build.status }.to('skipped')
- end
- end
- end
-
- (HasStatus::AVAILABLE_STATUSES - %w[created pending]).each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, :actionable, user: user, project: project) }
+ context 'when current status is failed' do
+ let(:current_status) { 'failed' }
- it 'does not change build status' do
- expect { subject }.not_to change { build.status }
- end
- end
- end
+ it 'does not change the build status' do
+ expect { subject }.to change { build.status }.to('pending')
end
end
end
- shared_examples_for 'Scheduling properly' do |valid_statuses_for_when|
- valid_statuses_for_when.each do |status_for_prior_stages|
- context "when status for prior stages is #{status_for_prior_stages}" do
- let(:current_status) { status_for_prior_stages }
-
- %w[created].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, :schedulable, user: user, project: project) }
-
- it 'enqueues the build' do
- expect { subject }.to change { build.status }.to('scheduled')
- end
- end
- end
+ context 'when build has always option' do
+ let(:build) { create(:ci_build, :created, when: :always, user: user, project: project) }
- %w[manual skipped pending running success failed canceled scheduled].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, :schedulable, user: user, project: project) }
+ context 'when current status is success' do
+ let(:current_status) { 'success' }
- it 'does not change the build status' do
- expect { subject }.not_to change { build.status }
- end
- end
- end
+ it 'changes the build status' do
+ expect { subject }.to change { build.status }.to('pending')
end
end
- (HasStatus::AVAILABLE_STATUSES - valid_statuses_for_when).each do |status_for_prior_stages|
- let(:current_status) { status_for_prior_stages }
+ context 'when current status is failed' do
+ let(:current_status) { 'failed' }
- context "when status for prior stages is #{status_for_prior_stages}" do
- %w[created pending].each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, :schedulable, user: user, project: project) }
-
- it 'skips the build' do
- expect { subject }.to change { build.status }.to('skipped')
- end
- end
- end
-
- (HasStatus::AVAILABLE_STATUSES - %w[created pending]).each do |status|
- context "when build status is #{status}" do
- let(:build) { create(:ci_build, status.to_sym, :schedulable, user: user, project: project) }
-
- it 'does not change build status' do
- expect { subject }.not_to change { build.status }
- end
- end
- end
+ it 'does not change the build status' do
+ expect { subject }.to change { build.status }.to('pending')
end
end
end
- context 'when build has on_success option' do
- let(:when_option) { :on_success }
-
- it_behaves_like 'Enqueuing properly', %w[success skipped]
- end
-
- context 'when build has on_failure option' do
- let(:when_option) { :on_failure }
-
- it_behaves_like 'Enqueuing properly', %w[failed]
- end
+ context 'when build has manual option' do
+ let(:build) { create(:ci_build, :created, :actionable, user: user, project: project) }
- context 'when build has always option' do
- let(:when_option) { :always }
+ context 'when current status is success' do
+ let(:current_status) { 'success' }
- it_behaves_like 'Enqueuing properly', %w[success failed skipped]
- end
+ it 'changes the build status' do
+ expect { subject }.to change { build.status }.to('manual')
+ end
+ end
- context 'when build has manual option' do
- let(:when_option) { :manual }
+ context 'when current status is failed' do
+ let(:current_status) { 'failed' }
- it_behaves_like 'Actionizing properly', %w[success skipped]
+ it 'does not change the build status' do
+ expect { subject }.to change { build.status }.to('skipped')
+ end
+ end
end
context 'when build has delayed option' do
- let(:when_option) { :delayed }
-
before do
allow(Ci::BuildScheduleWorker).to receive(:perform_at) { }
end
+ let(:build) { create(:ci_build, :created, :schedulable, user: user, project: project) }
+
context 'when ci_enable_scheduled_build is enabled' do
before do
stub_feature_flags(ci_enable_scheduled_build: true)
end
- it_behaves_like 'Scheduling properly', %w[success skipped]
+ context 'when current status is success' do
+ let(:current_status) { 'success' }
+
+ it 'changes the build status' do
+ expect { subject }.to change { build.status }.to('scheduled')
+ end
+ end
+
+ context 'when current status is failed' do
+ let(:current_status) { 'failed' }
+
+ it 'does not change the build status' do
+ expect { subject }.to change { build.status }.to('skipped')
+ end
+ end
end
- context 'when ci_enable_scheduled_build is enabled' do
+ context 'when ci_enable_scheduled_build is disabled' do
before do
stub_feature_flags(ci_enable_scheduled_build: false)
end
- it_behaves_like 'Actionizing properly', %w[success skipped]
+ context 'when current status is success' do
+ let(:current_status) { 'success' }
+
+ it 'changes the build status' do
+ expect { subject }.to change { build.status }.to('manual')
+ end
+ end
+
+ context 'when current status is failed' do
+ let(:current_status) { 'failed' }
+
+ it 'does not change the build status' do
+ expect { subject }.to change { build.status }.to('skipped')
+ end
+ end
end
end
end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 642de81ed52..368abded448 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -27,6 +27,7 @@ describe Ci::RetryBuildService do
job_artifacts_metadata job_artifacts_trace job_artifacts_junit
job_artifacts_sast job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast
+ job_artifacts_license_management job_artifacts_performance
job_artifacts_codequality scheduled_at].freeze
IGNORE_ACCESSORS =
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index 7c5c7409cc1..84cfa53ea05 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -24,6 +24,12 @@ describe Groups::UpdateService do
expect(TodosDestroyer::GroupPrivateWorker).not_to receive(:perform_in)
end
+
+ it "returns false if save failed" do
+ allow(public_group).to receive(:save).and_return(false)
+
+ expect(service.execute).to be_falsey
+ end
end
context "internal group with internal project" do
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
new file mode 100644
index 00000000000..b4718a07204
--- /dev/null
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::AfterRenameService do
+ let(:rugged_config) { rugged_repo(project.repository).config }
+
+ describe '#execute' do
+ context 'using legacy storage' do
+ let(:project) { create(:project, :repository, :legacy_storage) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:project_storage) { project.send(:storage) }
+
+ before do
+ # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
+ # call. This makes testing a bit easier.
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+
+ allow(project)
+ .to receive(:previous_changes)
+ .and_return('path' => ['foo'])
+
+ allow(project)
+ .to receive(:path_was)
+ .and_return('foo')
+
+ stub_feature_flags(skip_hashed_storage_upgrade: false)
+ end
+
+ it 'renames a repository' do
+ stub_container_registry_config(enabled: false)
+
+ expect(gitlab_shell).to receive(:mv_repository)
+ .ordered
+ .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}")
+ .and_return(true)
+
+ expect(gitlab_shell).to receive(:mv_repository)
+ .ordered
+ .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
+ .and_return(true)
+
+ expect_any_instance_of(SystemHooksService)
+ .to receive(:execute_hooks_for)
+ .with(project, :rename)
+
+ expect_any_instance_of(Gitlab::UploadsTransfer)
+ .to receive(:rename_project)
+ .with('foo', project.path, project.namespace.full_path)
+
+ expect(project).to receive(:expire_caches_before_rename)
+
+ described_class.new(project).execute
+ end
+
+ context 'container registry with images' do
+ let(:container_repository) { create(:container_repository) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: :any, tags: ['tag'])
+ project.container_repositories << container_repository
+ end
+
+ it 'raises a RenameFailedError' do
+ expect { described_class.new(project).execute }
+ .to raise_error(described_class::RenameFailedError)
+ end
+ end
+
+ context 'gitlab pages' do
+ before do
+ expect(project_storage).to receive(:rename_repo) { true }
+ end
+
+ it 'moves pages folder to new location' do
+ expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
+
+ described_class.new(project).execute
+ end
+ end
+
+ context 'attachments' do
+ before do
+ expect(project_storage).to receive(:rename_repo) { true }
+ end
+
+ it 'moves uploads folder to new location' do
+ expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project)
+
+ described_class.new(project).execute
+ end
+ end
+
+ it 'updates project full path in .git/config' do
+ allow(project_storage).to receive(:rename_repo).and_return(true)
+
+ described_class.new(project).execute
+
+ expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
+ end
+ end
+
+ context 'using hashed storage' do
+ let(:project) { create(:project, :repository, skip_disk_validation: true) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) }
+ let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) }
+ let(:hashed_path) { File.join(hashed_prefix, hash) }
+
+ before do
+ # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
+ # call. This makes testing a bit easier.
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+ allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+
+ stub_feature_flags(skip_hashed_storage_upgrade: false)
+ stub_application_setting(hashed_storage_enabled: true)
+ end
+
+ context 'migration to hashed storage' do
+ it 'calls HashedStorageMigrationService with correct options' do
+ project = create(:project, :repository, :legacy_storage)
+ allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+
+ expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service|
+ expect(service).to receive(:execute).and_return(true)
+ end
+
+ described_class.new(project).execute
+ end
+ end
+
+ it 'renames a repository' do
+ stub_container_registry_config(enabled: false)
+
+ expect(gitlab_shell).not_to receive(:mv_repository)
+
+ expect_any_instance_of(SystemHooksService)
+ .to receive(:execute_hooks_for)
+ .with(project, :rename)
+
+ expect(project).to receive(:expire_caches_before_rename)
+
+ described_class.new(project).execute
+ end
+
+ context 'container registry with images' do
+ let(:container_repository) { create(:container_repository) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: :any, tags: ['tag'])
+ project.container_repositories << container_repository
+ end
+
+ it 'raises a RenameFailedError' do
+ expect { described_class.new(project).execute }
+ .to raise_error(described_class::RenameFailedError)
+ end
+ end
+
+ context 'gitlab pages' do
+ it 'moves pages folder to new location' do
+ expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
+
+ described_class.new(project).execute
+ end
+ end
+
+ context 'attachments' do
+ it 'keeps uploads folder location unchanged' do
+ expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project)
+
+ described_class.new(project).execute
+ end
+
+ context 'when not rolled out' do
+ let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }
+
+ it 'moves pages folder to hashed storage' do
+ expect_next_instance_of(Projects::HashedStorage::MigrateAttachmentsService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ described_class.new(project).execute
+ end
+ end
+ end
+
+ it 'updates project full path in .git/config' do
+ described_class.new(project).execute
+
+ expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index a80c8a7fe51..08de27ca44a 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -295,6 +295,17 @@ describe Projects::CreateService, '#execute' do
end
end
+ it 'calls the passed block' do
+ fake_block = double('block')
+ opts[:relations_block] = fake_block
+
+ expect_next_instance_of(Project) do |project|
+ expect(fake_block).to receive(:call).with(project)
+ end
+
+ create_project(user, opts)
+ end
+
it 'writes project full path to .git/config' do
project = create_project(user, opts)
rugged = rugged_repo(project.repository)
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 1d31d26f418..12ddf8447bd 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -259,7 +259,6 @@ describe Projects::DestroyService do
before do
project.lfs_objects << create(:lfs_object)
- forked_project.forked_project_link.destroy
forked_project.reload
end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 947cb61038d..a3d24ae312a 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -31,6 +31,10 @@ describe Projects::ForkService do
it { is_expected.not_to be_persisted }
it { expect(subject.errors[:forked_from_project_id]).to eq(['is forbidden']) }
+
+ it 'does not create a fork network' do
+ expect { subject }.not_to change { @from_project.reload.fork_network }
+ end
end
describe "successfully creates project in the user namespace" do
@@ -70,6 +74,12 @@ describe Projects::ForkService do
expect(fork_network.root_project).to eq(@from_project)
expect(fork_network.projects).to contain_exactly(@from_project, to_project)
end
+
+ it 'imports the repository of the forked project' do
+ to_project = fork_project(@from_project, @to_user, repository: true)
+
+ expect(to_project.empty_repo?).to be_falsy
+ end
end
context 'creating a fork of a fork' do
@@ -247,11 +257,13 @@ describe Projects::ForkService do
context 'if project is not forked' do
it 'creates fork relation' do
- expect(fork_to_project.forked?).to be false
+ expect(fork_to_project.forked?).to be_falsy
expect(forked_from_project(fork_to_project)).to be_nil
subject.execute(fork_to_project)
+ fork_to_project.reload
+
expect(fork_to_project.forked?).to be true
expect(forked_from_project(fork_to_project)).to eq fork_from_project
expect(fork_to_project.forked_from_project).to eq fork_from_project
@@ -272,6 +284,17 @@ describe Projects::ForkService do
.to change { fork_to_project.lfs_objects_projects.count }
.to(0)
end
+
+ context 'if the fork is not allowed' do
+ let(:fork_from_project) { create(:project, :private) }
+
+ it 'does not delete the LFS objects' do
+ create(:lfs_objects_project, project: fork_to_project)
+
+ expect { subject.execute(fork_to_project) }
+ .not_to change { fork_to_project.lfs_objects_projects.size }
+ end
+ end
end
end
end
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index 3ec6139bfa6..014aab44281 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -5,13 +5,12 @@ describe Projects::UnlinkForkService do
subject { described_class.new(forked_project, user) }
- let(:fork_link) { forked_project.forked_project_link }
let(:project) { create(:project, :public) }
let(:forked_project) { fork_project(project, user) }
let(:user) { create(:user) }
context 'with opened merge request on the source project' do
- let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: fork_link.forked_from_project) }
+ let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: forked_project.forked_from_project) }
let(:merge_request2) { create(:merge_request, source_project: forked_project, target_project: fork_project(project)) }
let(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }
@@ -35,12 +34,6 @@ describe Projects::UnlinkForkService do
end
end
- it 'remove fork relation' do
- expect(forked_project.forked_project_link).to receive(:destroy)
-
- subject.execute
- end
-
it 'removes the link to the fork network' do
expect(forked_project.fork_network_member).to be_present
expect(forked_project.fork_network).to be_present
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index ac0ca1f33a5..41a170e4f25 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -380,6 +380,14 @@ describe QuickActions::InterpretService do
end
end
+ shared_examples 'assign command' do
+ it 'assigns to a single user' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(assignee_ids: [developer.id])
+ end
+ end
+
it_behaves_like 'reopen command' do
let(:content) { '/reopen' }
let(:issuable) { issue }
@@ -474,67 +482,56 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- context 'assign command' do
- let(:content) { "/assign @#{developer.username}" }
-
- context 'Issue' do
- it 'fetches assignee and populates assignee_ids if content contains /assign' do
- _, updates = service.execute(content, issue)
-
- expect(updates[:assignee_ids]).to match_array([developer.id])
- end
+ context 'assign command with one user' do
+ it_behaves_like 'assign command' do
+ let(:content) { "/assign @#{developer.username}" }
+ let(:issuable) { issue }
end
- context 'Merge Request' do
- it 'fetches assignee and populates assignee_ids if content contains /assign' do
- _, updates = service.execute(content, merge_request)
-
- expect(updates).to eq(assignee_ids: [developer.id])
- end
+ it_behaves_like 'assign command' do
+ let(:content) { "/assign @#{developer.username}" }
+ let(:issuable) { merge_request }
end
end
+ # CE does not have multiple assignees
context 'assign command with multiple assignees' do
- let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
-
before do
project.add_developer(developer2)
end
- context 'Issue' do
- it 'fetches assignee and populates assignee_ids if content contains /assign' do
- _, updates = service.execute(content, issue)
-
- expect(updates[:assignee_ids]).to match_array([developer.id])
- end
+ it_behaves_like 'assign command' do
+ let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
+ let(:issuable) { issue }
end
- context 'Merge Request' do
- it 'fetches assignee and populates assignee_ids if content contains /assign' do
- _, updates = service.execute(content, merge_request)
-
- expect(updates).to eq(assignee_ids: [developer.id])
- end
+ it_behaves_like 'assign command' do
+ let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
+ let(:issuable) { merge_request }
end
end
context 'assign command with me alias' do
- let(:content) { "/assign me" }
-
- context 'Issue' do
- it 'fetches assignee and populates assignee_ids if content contains /assign' do
- _, updates = service.execute(content, issue)
+ it_behaves_like 'assign command' do
+ let(:content) { '/assign me' }
+ let(:issuable) { issue }
+ end
- expect(updates).to eq(assignee_ids: [developer.id])
- end
+ it_behaves_like 'assign command' do
+ let(:content) { '/assign me' }
+ let(:issuable) { merge_request }
end
+ end
- context 'Merge Request' do
- it 'fetches assignee and populates assignee_ids if content contains /assign' do
- _, updates = service.execute(content, merge_request)
+ context 'assign command with me alias and whitespace' do
+ it_behaves_like 'assign command' do
+ let(:content) { '/assign me ' }
+ let(:issuable) { issue }
+ end
- expect(updates).to eq(assignee_ids: [developer.id])
- end
+ it_behaves_like 'assign command' do
+ let(:content) { '/assign me ' }
+ let(:issuable) { merge_request }
end
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 622e56e1da5..b1cc6d2eb83 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -97,7 +97,7 @@ describe WebHookService do
end
it 'handles exceptions' do
- exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError]
+ exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep]
exceptions.each do |exception_class|
exception = exception_class.new('Exception message')
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index 1c1b68c12a2..140490f2dc2 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -22,6 +22,18 @@ shared_examples 'a GitHub-ish import controller: POST personal_access_token' do
expect(session[:"#{provider}_access_token"]).to eq(token)
expect(controller).to redirect_to(status_import_url)
end
+
+ it "strips access token with spaces" do
+ token = 'asdfasdf9876'
+
+ allow_any_instance_of(Gitlab::LegacyGithubImport::Client)
+ .to receive(:user).and_return(true)
+
+ post :personal_access_token, personal_access_token: " #{token} "
+
+ expect(session[:"#{provider}_access_token"]).to eq(token)
+ expect(controller).to redirect_to(status_import_url)
+ end
end
shared_examples 'a GitHub-ish import controller: GET new' do
diff --git a/spec/support/helpers/ci_artifact_metadata_generator.rb b/spec/support/helpers/ci_artifact_metadata_generator.rb
new file mode 100644
index 00000000000..ef638d59d2d
--- /dev/null
+++ b/spec/support/helpers/ci_artifact_metadata_generator.rb
@@ -0,0 +1,48 @@
+# frozen_sting_literal: true
+
+# This generates fake CI metadata .gz for testing
+# Based off https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/internal/zipartifacts/metadata.go
+class CiArtifactMetadataGenerator
+ attr_accessor :entries, :output
+
+ ARTIFACT_METADATA = "GitLab Build Artifacts Metadata 0.0.2\n".freeze
+
+ def initialize(stream)
+ @entries = {}
+ @output = Zlib::GzipWriter.new(stream)
+ end
+
+ def add_entry(filename)
+ @entries[filename] = { CRC: rand(0xfffffff), Comment: FFaker::Lorem.sentence(10) }
+ end
+
+ def write
+ write_version
+ write_errors
+ write_entries
+ output.close
+ end
+
+ private
+
+ def write_version
+ write_string(ARTIFACT_METADATA)
+ end
+
+ def write_errors
+ write_string('{}')
+ end
+
+ def write_entries
+ entries.each do |filename, metadata|
+ write_string(filename)
+ write_string(metadata.to_json + "\n")
+ end
+ end
+
+ def write_string(data)
+ bytes = [data.length].pack('L>')
+ output.write(bytes)
+ output.write(data)
+ end
+end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index c077ca9f15b..a03d9c4045f 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -33,10 +33,11 @@ module KubernetesHelpers
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
- def stub_kubeclient_get_secret(api_url, namespace: 'default', **options)
+ def stub_kubeclient_get_secret(api_url, **options)
options[:metadata_name] ||= "default-token-1"
+ options[:namespace] ||= "default"
- WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{options[:metadata_name]}")
+ WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}")
.to_return(kube_response(kube_v1_secret_body(options)))
end
@@ -65,6 +66,21 @@ module KubernetesHelpers
.to_return(kube_response({}))
end
+ def stub_kubeclient_create_role_binding(api_url, namespace: 'default')
+ WebMock.stub_request(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings")
+ .to_return(kube_response({}))
+ end
+
+ def stub_kubeclient_create_namespace(api_url)
+ WebMock.stub_request(:post, api_url + "/api/v1/namespaces")
+ .to_return(kube_response({}))
+ end
+
+ def stub_kubeclient_get_namespace(api_url, namespace: 'default')
+ WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}")
+ .to_return(kube_response({}))
+ end
+
def kube_v1_secret_body(**options)
{
"kind" => "SecretList",
@@ -87,7 +103,8 @@ module KubernetesHelpers
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
{ "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" },
- { "name" => "services", "namespaced" => true, "kind" => "Service" }
+ { "name" => "services", "namespaced" => true, "kind" => "Service" },
+ { "name" => "namespaces", "namespaced" => true, "kind" => "Namespace" }
]
}
end
diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb
index 2c501a2a27c..6a7132c3093 100644
--- a/spec/support/helpers/project_forks_helper.rb
+++ b/spec/support/helpers/project_forks_helper.rb
@@ -24,7 +24,7 @@ module ProjectForksHelper
allow(service).to receive(:gitlab_shell).and_return(shell)
end
- forked_project = service.execute
+ forked_project = service.execute(params[:target_project])
# Reload the both projects so they know about their newly created fork_network
if forked_project.persisted?
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 97875669d0e..71d72ff27e9 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -31,6 +31,8 @@ module TestEnv
'symlink-expand-diff' => '81e6355',
'expand-collapse-files' => '025db92',
'expand-collapse-lines' => '238e82d',
+ 'pages-deploy' => '7897d5b',
+ 'pages-deploy-target' => '7975be0',
'video' => '8879059',
'add-balsamiq-file' => 'b89b56d',
'crlf-diff' => '5938907',
@@ -68,7 +70,6 @@ module TestEnv
TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**')
REPOS_STORAGE = 'default'.freeze
- BROKEN_STORAGE = 'broken'.freeze
# Test environment
#
@@ -157,10 +158,6 @@ module TestEnv
version: Gitlab::GitalyClient.expected_server_version,
task: "gitlab:gitaly:install[#{gitaly_dir},#{repos_path}]") do
- # Re-create config, to specify the broken storage path
- storage_paths = { 'default' => repos_path, 'broken' => broken_path }
- Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, storage_paths, force: true)
-
start_gitaly(gitaly_dir)
end
end
@@ -171,6 +168,8 @@ module TestEnv
return
end
+ FileUtils.mkdir_p("tmp/tests/second_storage") unless File.exist?("tmp/tests/second_storage")
+
spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s
Bundler.with_original_env do
raise "gitaly spawn failed" unless system(spawn_script)
@@ -255,10 +254,6 @@ module TestEnv
@repos_path ||= Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path
end
- def broken_path
- @broken_path ||= Gitlab.config.repositories.storages[BROKEN_STORAGE].legacy_disk_path
- end
-
def backup_path
Gitlab.config.backup.path
end
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
new file mode 100644
index 00000000000..9c9d7ad781e
--- /dev/null
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -0,0 +1,54 @@
+shared_examples 'issuable notes filter' do
+ it 'sets discussion filter' do
+ notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter
+
+ expect(user.reload.notes_filter_for(issuable)).to eq(notes_filter)
+ expect(UserPreference.count).to eq(1)
+ end
+
+ it 'expires notes e-tag cache for issuable if filter changed' do
+ notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
+
+ expect_any_instance_of(issuable.class).to receive(:expire_note_etag_cache)
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter
+ end
+
+ it 'does not expires notes e-tag cache for issuable if filter did not change' do
+ notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
+ user.set_notes_filter(notes_filter, issuable)
+
+ expect_any_instance_of(issuable.class).not_to receive(:expire_note_etag_cache)
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter
+ end
+
+ it 'does not set notes filter when database is in read only mode' do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter
+
+ expect(user.reload.notes_filter_for(issuable)).to eq(0)
+ end
+
+ it 'returns no system note' do
+ user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
+
+ expect(JSON.parse(response.body).count).to eq(1)
+ end
+
+ context 'when filter is set to "only_comments"' do
+ it 'does not merge label event notes' do
+ user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
+
+ expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new)
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
+ end
+ end
+end
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index 6a9ad43941d..55212355daa 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -1,8 +1,4 @@
RSpec.configure do |config|
- config.before(:all, :broken_storage) do
- FileUtils.rm_rf Gitlab.config.repositories.storages.broken.legacy_disk_path
- end
-
config.before(:each, :broken_storage) do
allow(Gitlab::GitalyClient).to receive(:call) do
raise GRPC::Unavailable.new('Gitaly broken in this spec')
diff --git a/spec/views/shared/runners/show.html.haml_spec.rb b/spec/views/shared/runners/show.html.haml_spec.rb
new file mode 100644
index 00000000000..5e92928b143
--- /dev/null
+++ b/spec/views/shared/runners/show.html.haml_spec.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'shared/runners/show.html.haml' do
+ include PageLayoutHelper
+
+ let(:runner) do
+ create(:ci_runner, name: 'test runner',
+ version: '11.4.0',
+ ip_address: '127.1.2.3',
+ revision: 'abcd1234',
+ architecture: 'amd64' )
+ end
+
+ before do
+ assign(:runner, runner)
+ end
+
+ subject do
+ render
+ rendered
+ end
+
+ describe 'Page title' do
+ before do
+ expect_any_instance_of(PageLayoutHelper).to receive(:page_title).with("#{runner.description} ##{runner.id}", 'Runners')
+ end
+
+ it 'sets proper page title' do
+ render
+ end
+ end
+
+ describe 'Runner id and type' do
+ context 'when runner is of type instance' do
+ it { is_expected.to have_content("Runner ##{runner.id} Shared") }
+ end
+
+ context 'when runner is of type group' do
+ let(:runner) { create(:ci_runner, :group) }
+
+ it { is_expected.to have_content("Runner ##{runner.id} Group") }
+ end
+
+ context 'when runner is of type project' do
+ let(:runner) { create(:ci_runner, :project) }
+
+ it { is_expected.to have_content("Runner ##{runner.id} Specific") }
+ end
+ end
+
+ describe 'Active value' do
+ context 'when runner is active' do
+ it { is_expected.to have_content('Active Yes') }
+ end
+
+ context 'when runner is inactive' do
+ let(:runner) { create(:ci_runner, :inactive) }
+
+ it { is_expected.to have_content('Active No') }
+ end
+ end
+
+ describe 'Protected value' do
+ context 'when runner is not protected' do
+ it { is_expected.to have_content('Protected No') }
+ end
+
+ context 'when runner is protected' do
+ let(:runner) { create(:ci_runner, :ref_protected) }
+
+ it { is_expected.to have_content('Protected Yes') }
+ end
+ end
+
+ describe 'Can run untagged jobs value' do
+ context 'when runner run untagged job is set' do
+ it { is_expected.to have_content('Can run untagged jobs Yes') }
+ end
+
+ context 'when runner run untagged job is unset' do
+ let(:runner) { create(:ci_runner, :tagged_only) }
+
+ it { is_expected.to have_content('Can run untagged jobs No') }
+ end
+ end
+
+ describe 'Locked to this project value' do
+ context 'when runner locked is not set' do
+ it { is_expected.to have_content('Locked to this project No') }
+
+ context 'when runner is of type group' do
+ let(:runner) { create(:ci_runner, :group) }
+
+ it { is_expected.not_to have_content('Locked to this project') }
+ end
+ end
+
+ context 'when runner locked is set' do
+ let(:runner) { create(:ci_runner, :locked) }
+
+ it { is_expected.to have_content('Locked to this project Yes') }
+
+ context 'when runner is of type group' do
+ let(:runner) { create(:ci_runner, :group, :locked) }
+
+ it { is_expected.not_to have_content('Locked to this project') }
+ end
+ end
+ end
+
+ describe 'Tags value' do
+ context 'when runner does not have tags' do
+ it { is_expected.to have_content('Tags') }
+ it { is_expected.not_to have_selector('span.badge.badge-primary')}
+ end
+
+ context 'when runner have tags' do
+ let(:runner) { create(:ci_runner, tag_list: %w(tag2 tag3 tag1)) }
+
+ it { is_expected.to have_content('Tags tag1 tag2 tag3') }
+ it { is_expected.to have_selector('span.badge.badge-primary')}
+ end
+ end
+
+ describe 'Metadata values' do
+ it { is_expected.to have_content("Name #{runner.name}") }
+ it { is_expected.to have_content("Version #{runner.version}") }
+ it { is_expected.to have_content("IP Address #{runner.ip_address}") }
+ it { is_expected.to have_content("Revision #{runner.revision}") }
+ it { is_expected.to have_content("Platform #{runner.platform}") }
+ it { is_expected.to have_content("Architecture #{runner.architecture}") }
+ it { is_expected.to have_content("Description #{runner.description}") }
+ end
+
+ describe 'Maximum job timeout value' do
+ let(:runner) { create(:ci_runner, maximum_timeout: 5400) }
+
+ it { is_expected.to have_content('Maximum job timeout 1h 30m') }
+ end
+
+ describe 'Last contact value' do
+ context 'when runner have not contacted yet' do
+ it { is_expected.to have_content('Last contact Never') }
+ end
+
+ context 'when runner have already contacted' do
+ let(:runner) { create(:ci_runner, contacted_at: DateTime.now - 6.days) }
+ let(:expected_contacted_at) { I18n.localize(runner.contacted_at, format: "%b %d, %Y") }
+
+ it { is_expected.to have_content("Last contact #{expected_contacted_at}") }
+ end
+ end
+end
diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb
index eec110dfbfb..2f21a1321e1 100644
--- a/spec/workers/namespaceless_project_destroy_worker_spec.rb
+++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb
@@ -71,10 +71,10 @@ describe NamespacelessProjectDestroyWorker do
expect(merge_request.reload).to be_closed
end
- it 'destroys the link' do
+ it 'destroys fork network members' do
subject.perform(project.id)
- expect(parent_project.forked_project_links).to be_empty
+ expect(parent_project.forked_to_members).to be_empty
end
end
end
diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb
index 20aff020dbb..936b9deaecc 100644
--- a/spec/workers/rebase_worker_spec.rb
+++ b/spec/workers/rebase_worker_spec.rb
@@ -1,25 +1,23 @@
require 'spec_helper'
describe RebaseWorker, '#perform' do
+ include ProjectForksHelper
+
context 'when rebasing an MR from a fork where upstream has protected branches' do
let(:upstream_project) { create(:project, :repository) }
- let(:fork_project) { create(:project, :repository) }
+ let(:forked_project) { fork_project(upstream_project, nil, repository: true) }
let(:merge_request) do
create(:merge_request,
- source_project: fork_project,
+ source_project: forked_project,
source_branch: 'feature_conflict',
target_project: upstream_project,
target_branch: 'master')
end
- before do
- create(:forked_project_link, forked_to_project: fork_project, forked_from_project: upstream_project)
- end
-
it 'sets the correct project for running hooks' do
expect(MergeRequests::RebaseService)
- .to receive(:new).with(fork_project, merge_request.author).and_call_original
+ .to receive(:new).with(forked_project, merge_request.author).and_call_original
subject.perform(merge_request, merge_request.author)
end
diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb
index ede271b2cdd..50b93fce2dc 100644
--- a/spec/workers/repository_check/batch_worker_spec.rb
+++ b/spec/workers/repository_check/batch_worker_spec.rb
@@ -51,7 +51,7 @@ describe RepositoryCheck::BatchWorker do
it 'does nothing when shard is unhealthy' do
shard_name = 'broken'
- create(:project, created_at: 1.week.ago, repository_storage: shard_name)
+ create(:project, :broken_storage, created_at: 1.week.ago)
expect(subject.perform(shard_name)).to eq(nil)
end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index ac8716ecfb1..781f91ac9ca 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe RepositoryForkWorker do
+ include ProjectForksHelper
+
describe 'modules' do
it 'includes ProjectImportOptions' do
expect(described_class).to include_module(ProjectImportOptions)
@@ -8,9 +10,13 @@ describe RepositoryForkWorker do
end
describe "#perform" do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :public, :repository) }
let(:shell) { Gitlab::Shell.new }
- let(:fork_project) { create(:project, :repository, :import_scheduled, forked_from_project: project) }
+ let(:forked_project) { create(:project, :repository, :import_scheduled) }
+
+ before do
+ fork_project(project, forked_project.creator, target_project: forked_project, repository: true)
+ end
shared_examples 'RepositoryForkWorker performing' do
before do
@@ -21,8 +27,8 @@ describe RepositoryForkWorker do
expect(shell).to receive(:fork_repository).with(
'default',
project.disk_path,
- fork_project.repository_storage,
- fork_project.disk_path
+ forked_project.repository_storage,
+ forked_project.disk_path
)
end
@@ -49,28 +55,28 @@ describe RepositoryForkWorker do
perform!
- expect(fork_project.protected_branches.first.name).to eq(fork_project.default_branch)
+ expect(forked_project.protected_branches.first.name).to eq(forked_project.default_branch)
end
it 'flushes various caches' do
expect_fork_repository.and_return(true)
# Works around https://github.com/rspec/rspec-mocks/issues/910
- expect(Project).to receive(:find).with(fork_project.id).and_return(fork_project)
- expect(fork_project.repository).to receive(:expire_emptiness_caches)
+ expect(Project).to receive(:find).with(forked_project.id).and_return(forked_project)
+ expect(forked_project.repository).to receive(:expire_emptiness_caches)
.and_call_original
- expect(fork_project.repository).to receive(:expire_exists_cache)
+ expect(forked_project.repository).to receive(:expire_exists_cache)
.and_call_original
- expect(fork_project.wiki.repository).to receive(:expire_emptiness_caches)
+ expect(forked_project.wiki.repository).to receive(:expire_emptiness_caches)
.and_call_original
- expect(fork_project.wiki.repository).to receive(:expire_exists_cache)
+ expect(forked_project.wiki.repository).to receive(:expire_exists_cache)
.and_call_original
perform!
end
it "handles bad fork" do
- error_message = "Unable to fork project #{fork_project.id} for repository #{project.disk_path} -> #{fork_project.disk_path}"
+ error_message = "Unable to fork project #{forked_project.id} for repository #{project.disk_path} -> #{forked_project.disk_path}"
expect_fork_repository.and_return(false)
@@ -80,7 +86,7 @@ describe RepositoryForkWorker do
context 'only project ID passed' do
def perform!
- subject.perform(fork_project.id)
+ subject.perform(forked_project.id)
end
it_behaves_like 'RepositoryForkWorker performing'
@@ -88,7 +94,7 @@ describe RepositoryForkWorker do
context 'project ID, storage and repo paths passed' do
def perform!
- subject.perform(fork_project.id, TestEnv.repos_path, project.disk_path)
+ subject.perform(forked_project.id, TestEnv.repos_path, project.disk_path)
end
it_behaves_like 'RepositoryForkWorker performing'
diff --git a/yarn.lock b/yarn.lock
index 292c7128d18..5da401c1d43 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -616,11 +616,16 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
-"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.32.0":
+"@gitlab-org/gitlab-svgs@^1.23.0":
version "1.32.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.32.0.tgz#a65ab7724fa7d55be8e5cc9b2dbe3f0757432fd3"
integrity sha512-L3o8dFUd2nSkVZBwh2hCJWzNzADJ3dTBZxamND8NLosZK9/ohNhccmsQOZGyMCUHaOzm4vifaaXkAXh04UtMKA==
+"@gitlab-org/gitlab-svgs@^1.33.0":
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.33.0.tgz#068566e8ee00795f6f09f58236f08e1716f9f04a"
+ integrity sha512-8ajtUHk6gQ1xosL/CO5IzHSFM/t18hx5pfzQ3cd0VuQXcyR6QKGuXTLwbYdmJDYOw1Etoo5DqDWxPEClHyZpiA==
+
"@gitlab-org/gitlab-ui@^1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-ui/-/gitlab-ui-1.8.0.tgz#dee33d78f68c91644273dbd51734b796108263ee"
@@ -1325,18 +1330,6 @@ binaryextensions@2:
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==
-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"
- integrity sha1-NwezoRPXhhDjBo4YpYj0a0aI3kk=
- dependencies:
- d3 "3.5.17"
- dagre-d3-renderer "^0.4.24"
- dagre-layout "^0.8.0"
- he "^1.1.1"
- lodash "^4.17.4"
- moment "^2.18.1"
-
blob@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
@@ -2304,6 +2297,11 @@ d3-format@1, d3-format@1.2.1:
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f"
integrity sha512-U4zRVLDXW61bmqoo+OJ/V687e1T5nVd3TAKAJKgtpZ/P1JsMgyod0y9br+mlQOryTAACdiXI3wCjuERHFNp91w==
+d3-format@1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a"
+ integrity sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw==
+
d3-geo@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356"
@@ -2376,6 +2374,11 @@ d3-selection@1, d3-selection@1.2.0, d3-selection@^1.1.0, d3-selection@^1.2.0:
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.2.0.tgz#1b8ec1c7cedadfb691f2ba20a4a3cfbeb71bbc88"
integrity sha512-xW2Pfcdzh1gOaoI+LGpPsLR2VpBQxuFoxvrvguK8ZmrJbPIVvfNG6pU6GNfK41D6Qz15sj61sbW/AFYuukwaLQ==
+d3-selection@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d"
+ integrity sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA==
+
d3-shape@1.2.0, d3-shape@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
@@ -2428,11 +2431,6 @@ d3-zoom@1.7.1:
d3-selection "1"
d3-transition "1"
-d3@3.5.17:
- version "3.5.17"
- resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
- integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=
-
d3@4.12.2:
version "4.12.2"
resolved "https://registry.yarnpkg.com/d3/-/d3-4.12.2.tgz#12f775564c6a9de229f63db03446e2cb7bb56c8f"
@@ -2469,23 +2467,57 @@ d3@4.12.2:
d3-voronoi "1.1.2"
d3-zoom "1.7.1"
-dagre-d3-renderer@^0.4.24:
- version "0.4.24"
- resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.4.24.tgz#b36ce2fe4ea20de43e7698627c6ede2a9f15ec45"
- integrity sha512-QCrYq80NTyKph+m/+kNeEq2exw5HPo/x7XprJem3wDGJbEAJDKXI2pJpqe0R4k6AsjWVd5NMVL0X7feF24Zh6Q==
+d3@^4.13.0:
+ version "4.13.0"
+ resolved "https://registry.yarnpkg.com/d3/-/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d"
+ integrity sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ==
dependencies:
- d3 "3.5.17"
- dagre-layout "^0.8.0"
- graphlib "^2.1.1"
- lodash "^4.17.4"
+ d3-array "1.2.1"
+ d3-axis "1.0.8"
+ d3-brush "1.0.4"
+ d3-chord "1.0.4"
+ d3-collection "1.0.4"
+ d3-color "1.0.3"
+ d3-dispatch "1.0.3"
+ d3-drag "1.2.1"
+ d3-dsv "1.0.8"
+ d3-ease "1.0.3"
+ d3-force "1.1.0"
+ d3-format "1.2.2"
+ d3-geo "1.9.1"
+ d3-hierarchy "1.1.5"
+ d3-interpolate "1.1.6"
+ d3-path "1.0.5"
+ d3-polygon "1.0.3"
+ d3-quadtree "1.0.3"
+ d3-queue "3.0.7"
+ d3-random "1.1.0"
+ d3-request "1.0.6"
+ d3-scale "1.0.7"
+ d3-selection "1.3.0"
+ d3-shape "1.2.0"
+ d3-time "1.0.8"
+ d3-time-format "2.1.1"
+ d3-timer "1.0.7"
+ d3-transition "1.1.1"
+ d3-voronoi "1.1.2"
+ d3-zoom "1.7.1"
-dagre-layout@^0.8.0:
- version "0.8.0"
- resolved "https://registry.yarnpkg.com/dagre-layout/-/dagre-layout-0.8.0.tgz#7147b6afb655602f855158dfea171db9aa98d4ff"
- integrity sha512-vK4WiR6h3whkoW9aM/FCjZTTx10V2YnLOLEj2+uvOQmiEjGmUvkme+Qrjj/7Tq0+AI54yFHT/tbbqM9AadsK4A==
+dagre-d3-renderer@^0.5.8:
+ version "0.5.8"
+ resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.5.8.tgz#aa071bb71d3c4d67426925906f3f6ddead49c1a3"
+ integrity sha512-XH2a86isUHRxzIYbjQVEuZtJnWEufb64H5DuXIUmn8esuB40jgLEbUUclulWOW62/ZoXlj2ZDyL8SJ+YRxs+jQ==
dependencies:
- graphlib "^2.1.1"
- lodash "^4.17.4"
+ dagre-layout "^0.8.8"
+ lodash "^4.17.5"
+
+dagre-layout@^0.8.8:
+ version "0.8.8"
+ resolved "https://registry.yarnpkg.com/dagre-layout/-/dagre-layout-0.8.8.tgz#9b6792f24229f402441c14162c1049e3f261f6d9"
+ integrity sha512-ZNV15T9za7X+fV8Z07IZquUKugCxm5owoiPPxfEx6OJRD331nkiIaF3vSt0JEY5FkrY0KfRQxcpQ3SpXB7pLPQ==
+ dependencies:
+ graphlibrary "^2.2.0"
+ lodash "^4.17.5"
date-format@^1.2.0:
version "1.2.0"
@@ -3004,6 +3036,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+escaper@^2.5.3:
+ version "2.5.3"
+ resolved "https://registry.yarnpkg.com/escaper/-/escaper-2.5.3.tgz#8b8fe90ba364054151ab7eff18b4ce43b1e13ab5"
+ integrity sha512-QGb9sFxBVpbzMggrKTX0ry1oiI4CSDAl9vIL702hzl1jGW8VZs7qfqTRX7WDOjoNDoEVGcEtu1ZOQgReSfT2kQ==
+
escodegen@1.8.x:
version "1.8.1"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
@@ -3893,12 +3930,12 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
-graphlib@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.1.tgz#42352c52ba2f4d035cb566eb91f7395f76ebc951"
- integrity sha1-QjUsUrovTQNctWbrkfc5X3bryVE=
+graphlibrary@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/graphlibrary/-/graphlibrary-2.2.0.tgz#017a14899775228dec4497a39babfdd6bf56eac6"
+ integrity sha512-XTcvT55L8u4MBZrM37zXoUxsgxs/7sow7YSygd9CIwfWTVO8RVu7AYXhhCiTuFEf+APKgx6Jk4SuQbYR0vYKmQ==
dependencies:
- lodash "^4.11.1"
+ lodash "^4.17.5"
gzip-size@^5.0.0:
version "5.0.0"
@@ -4181,11 +4218,6 @@ ignore-walk@^3.0.1:
dependencies:
minimatch "^3.0.4"
-ignore@^3.3.7:
- version "3.3.8"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b"
- integrity sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==
-
ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
@@ -4567,6 +4599,11 @@ is-regex@^1.0.4:
dependencies:
has "^1.0.1"
+is-regexp@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
+ integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
+
is-resolvable@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
@@ -5134,7 +5171,7 @@ lodash.upperfirst@4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@^4.11.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
+lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@@ -5293,6 +5330,20 @@ merge-source-map@^1.1.0:
dependencies:
source-map "^0.6.1"
+mermaid@^8.0.0-rc.8:
+ version "8.0.0-rc.8"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0-rc.8.tgz#74ed54d0d46e9ee71c4db2730b2d83d516a21e72"
+ integrity sha512-GbF9jHWfqE7YGx9vQySmBxy2Ahlclxmpk4tJ9ntNyafENl96s96ggUK/NQS5ydYoFab6MavTm4YMTIPKqWVvPQ==
+ dependencies:
+ d3 "^4.13.0"
+ dagre-d3-renderer "^0.5.8"
+ dagre-layout "^0.8.8"
+ graphlibrary "^2.2.0"
+ he "^1.1.1"
+ lodash "^4.17.5"
+ moment "^2.21.0"
+ scope-css "^1.0.5"
+
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -5451,11 +5502,16 @@ mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
dependencies:
minimist "0.0.8"
-moment@2.x, moment@^2.18.1:
+moment@2.x:
version "2.19.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe"
integrity sha512-Rf6jiHPEfxp9+dlzxPTmRHbvoFXsh2L/U8hOupUMpnuecHQmI6cF6lUbJl3QqKPko1u6ujO+FxtcajLVfLpAtA==
+moment@^2.21.0:
+ version "2.22.2"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
+ integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
+
monaco-editor-webpack-plugin@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.5.4.tgz#6781a130e3e1379bb8f4cd190132f4af6dcd2c16"
@@ -6893,6 +6949,15 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
+scope-css@^1.0.5:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/scope-css/-/scope-css-1.2.1.tgz#c35768bc900cad030a3e0d663a818c0f6a57f40e"
+ integrity sha512-UjLRmyEYaDNiOS673xlVkZFlVCtckJR/dKgr434VMm7Lb+AOOqXKdAcY7PpGlJYErjXXJzKN7HWo4uRPiZZG0Q==
+ dependencies:
+ escaper "^2.5.3"
+ slugify "^1.3.1"
+ strip-css-comments "^3.0.0"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -7066,6 +7131,11 @@ slice-ansi@1.0.0:
dependencies:
is-fullwidth-code-point "^2.0.0"
+slugify@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.1.tgz#f572127e8535329fbc6c1edb74ab856b61ad7de2"
+ integrity sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw==
+
smooshpack@^0.0.48:
version "0.0.48"
resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.48.tgz#6fbeaaf59226a1fe500f56aa17185eed377d2823"
@@ -7439,6 +7509,13 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
+strip-css-comments@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-css-comments/-/strip-css-comments-3.0.0.tgz#7a5625eff8a2b226cf8947a11254da96e13dae89"
+ integrity sha1-elYl7/iisibPiUehElTaluE9rok=
+ dependencies:
+ is-regexp "^1.0.0"
+
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"